From 3e3ce188b1b032d11162a1d08cab160e888faddd Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 25 Mar 2016 16:49:58 -0400 Subject: [PATCH 01/19] Add scaleWidth and scaleHeight options to overlays. --- changelog.txt | 1 + src/overlay.js | 197 +++++++++++++++++++++-------------------- src/viewer.js | 8 +- test/demo/overlay.html | 56 ++++++++++-- 4 files changed, 156 insertions(+), 106 deletions(-) diff --git a/changelog.txt b/changelog.txt index abf513ec..744d09b2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -30,6 +30,7 @@ OPENSEADRAGON CHANGELOG * Fixed issue causing HTML pages to jump unwantedly to the reference strip upon loading (#872) * Added addOnceHandler method to EventSource (#887) * Added TiledImage.fitBounds method (#888) +* Added scaledWidth and scaleHeight options to Rect overlays to allow to scale in only one dimension. 2.1.0: diff --git a/src/overlay.js b/src/overlay.js index 7e121950..2166eb2a 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -32,7 +32,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -(function( $ ){ +(function($) { /** * An enumeration of positions that an overlay may be assigned relative to @@ -75,8 +75,14 @@ * check the size of the overlay everytime it is drawn when using a * {@link OpenSeadragon.Point} as options.location. It will improve * performances but will cause a misalignment if the overlay size changes. + * @param {Boolean} [options.scaleWidth=true] Whether the width of the + * overlay should be adjusted when the zoom changes when using a + * {@link OpenSeadragon.Rect} as options.location + * @param {Boolean} [options.scaleHeight=true] Whether the height of the + * overlay should be adjusted when the zoom changes when using a + * {@link OpenSeadragon.Rect} as options.location */ - $.Overlay = function( element, location, placement ) { + $.Overlay = function(element, location, placement) { /** * onDraw callback signature used by {@link OpenSeadragon.Overlay}. @@ -89,7 +95,7 @@ */ var options; - if ( $.isPlainObject( element ) ) { + if ($.isPlainObject(element)) { options = element; } else { options = { @@ -105,29 +111,35 @@ options.location.x, options.location.y, options.location.width, - options.location.height - ); - this.position = new $.Point( - options.location.x, - options.location.y - ); - this.size = new $.Point( - options.location.width, - options.location.height - ); - this.style = options.element.style; - // rects are always top-left - this.placement = options.location instanceof $.Point ? + options.location.height); + + // this.position is never read by this class but is kept for backward + // compatibility + this.position = this.bounds.getTopLeft(); + + // this.size is only used by PointOverlay with options.checkResize === false + this.size = this.bounds.getSize(); + + this.style = options.element.style; + + // rects are always top-left (RectOverlays don't use placement) + this.placement = options.location instanceof $.Point ? options.placement : $.Placement.TOP_LEFT; this.onDraw = options.onDraw; this.checkResize = options.checkResize === undefined ? true : options.checkResize; + this.scaleWidth = options.scaleWidth === undefined ? + true : options.scaleWidth; + this.scaleHeight = options.scaleHeight === undefined ? + true : options.scaleHeight; }; /** @lends OpenSeadragon.Overlay.prototype */ $.Overlay.prototype = { /** + * Internal function to adjust the position of a PointOverlay + * depending on it size and anchor. * @function * @param {OpenSeadragon.Point} position * @param {OpenSeadragon.Point} size @@ -153,20 +165,20 @@ * @function */ destroy: function() { - var element = this.element, - style = this.style; + var element = this.element; + var style = this.style; - if ( element.parentNode ) { - element.parentNode.removeChild( element ); + if (element.parentNode) { + element.parentNode.removeChild(element); //this should allow us to preserve overlays when required between //pages - if ( element.prevElementParent ) { + if (element.prevElementParent) { style.display = 'none'; //element.prevElementParent.insertBefore( // element, // element.prevNextSibling //); - document.body.appendChild( element ); + document.body.appendChild(element); } } @@ -177,9 +189,13 @@ style.left = ""; style.position = ""; - if ( this.scales ) { - style.width = ""; - style.height = ""; + if (this.scales) { + if (this.scaleWidth) { + style.width = ""; + } + if (this.scaleHeight) { + style.height = ""; + } } }, @@ -187,101 +203,88 @@ * @function * @param {Element} container */ - drawHTML: function( container, viewport ) { - var element = this.element, - style = this.style, - scales = this.scales, - degrees = viewport.degrees, - position = viewport.pixelFromPoint( - this.bounds.getTopLeft(), - true - ), - size, - overlayCenter; - - if ( element.parentNode != container ) { + drawHTML: function(container, viewport) { + var element = this.element; + if (element.parentNode !== container) { //save the source parent for later if we need it - element.prevElementParent = element.parentNode; - element.prevNextSibling = element.nextSibling; - container.appendChild( element ); - this.size = $.getElementSize( element ); + element.prevElementParent = element.parentNode; + element.prevNextSibling = element.nextSibling; + container.appendChild(element); + this.size = $.getElementSize(element); } - if ( scales ) { - size = viewport.deltaPixelsFromPoints( - this.bounds.getSize(), - true - ); - } else if ( this.checkResize ) { - size = $.getElementSize( element ); - } else { - size = this.size; - } + var positionAndSize = this.scales ? + this._getRectOverlayPositionAndSize(viewport) : + this._getPointOverlayPositionAndSize(viewport); - this.position = position; - this.size = size; + var position = this.position = positionAndSize.position; + var size = this.size = positionAndSize.size; - this.adjust( position, size ); - - position = position.apply( Math.round ); - size = size.apply( Math.round ); - - // rotate the position of the overlay - // TODO only rotate overlays if in canvas mode - // TODO replace the size rotation with CSS3 transforms - // TODO add an option to overlays to not rotate with the image - // Currently only rotates position and size - if( degrees !== 0 && this.scales ) { - overlayCenter = new $.Point( size.x / 2, size.y / 2 ); - - var drawerCenter = new $.Point( - viewport.viewer.drawer.canvas.width / 2, - viewport.viewer.drawer.canvas.height / 2 - ); - position = position.plus( overlayCenter ).rotate( - degrees, - drawerCenter - ).minus( overlayCenter ); - - size = size.rotate( degrees, new $.Point( 0, 0 ) ); - size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) ); - } + position = position.apply(Math.round); + size = size.apply(Math.round); // call the onDraw callback if it exists to allow one to overwrite // the drawing/positioning/sizing of the overlay - if ( this.onDraw ) { - this.onDraw( position, size, element ); + if (this.onDraw) { + this.onDraw(position, size, this.element); } else { - style.left = position.x + "px"; - style.top = position.y + "px"; + var style = this.style; + style.left = position.x + "px"; + style.top = position.y + "px"; + if (this.scales) { + if (this.scaleWidth) { + style.width = size.x + "px"; + } + if (this.scaleHeight) { + style.height = size.y + "px"; + } + } style.position = "absolute"; - if (style.display != 'none') { - style.display = 'block'; - } - - if ( scales ) { - style.width = size.x + "px"; - style.height = size.y + "px"; + if (style.display !== 'none') { + style.display = 'block'; } } }, + // private + _getRectOverlayPositionAndSize: function(viewport) { + return { + position: viewport.pixelFromPoint( + this.bounds.getTopLeft(), true), + size: viewport.deltaPixelsFromPoints( + this.bounds.getSize(), true) + }; + }, + + // private + _getPointOverlayPositionAndSize: function(viewport) { + var element = this.element; + var position = viewport.pixelFromPoint( + this.bounds.getTopLeft(), true); + var size = this.checkResize ? $.getElementSize(element) : this.size; + this.adjust(position, size); + return { + position: position, + size: size + }; + }, + /** + * Changes the location and placement of the overlay. * @function * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location * @param {OpenSeadragon.Placement} position */ - update: function( location, placement ) { - this.scales = location instanceof $.Rect; - this.bounds = new $.Rect( + update: function(location, placement) { + this.scales = location instanceof $.Rect; + this.bounds = new $.Rect( location.x, location.y, location.width, - location.height - ); + location.height); // rects are always top-left - this.placement = location instanceof $.Point ? + this.placement = location instanceof $.Point ? placement : $.Placement.TOP_LEFT; }, @@ -294,4 +297,4 @@ } }; -}( OpenSeadragon )); +}(OpenSeadragon)); diff --git a/src/viewer.js b/src/viewer.js index d3cce4b8..c3054768 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1788,7 +1788,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * is closed which include when changing page. * @method * @param {Element|String|Object} element - A reference to an element or an id for - * the element which will be overlayed. Or an Object specifying the configuration for the overlay + * the element which will be overlayed. Or an Object specifying the configuration for the overlay. + * If using an object, see {@link OpenSeadragon.Overlay} for a list of + * all available options. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or * rectangle which will be overlayed. This is a viewport relative location. * @param {OpenSeadragon.Placement} placement - The position of the @@ -2237,7 +2239,9 @@ function getOverlayObject( viewer, overlay ) { location: location, placement: placement, onDraw: overlay.onDraw, - checkResize: overlay.checkResize + checkResize: overlay.checkResize, + scaleWidth: overlay.scaleWidth, + scaleHeight: overlay.scaleHeight }); } diff --git a/test/demo/overlay.html b/test/demo/overlay.html index a3be7724..d2dde513 100644 --- a/test/demo/overlay.html +++ b/test/demo/overlay.html @@ -14,7 +14,7 @@
- +
From cac5f6dec3d86fa6972434e75b113f4d65262a12 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 28 Mar 2016 17:06:59 -0400 Subject: [PATCH 02/19] Add overlays rotation support. --- src/overlay.js | 88 +++++++++++++++++++++++++++++++++++++++++++++----- src/viewer.js | 3 +- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index 2166eb2a..7ee7941a 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -55,6 +55,23 @@ */ $.OverlayPlacement = $.Placement; + /** + * An enumeration of possible ways to handle overlays rotation + * @memberOf OpenSeadragon + * @static + * @property {Number} NO_ROTATION The overlay ignore the viewport rotation. + * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with + * the viewport. If the overlay contains text, it will get rotated as well. + * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by + * taking the size of the bounding box of the rotated bounds. + * Only valid for overlays with Rect location and scalable in both directions. + */ + $.OverlayRotationMode = { + NO_ROTATION: 1, + EXACT: 2, + BOUNDING_BOX: 3 + }; + /** * @class Overlay * @classdesc Provides a way to float an HTML element on top of the viewer element. @@ -81,6 +98,8 @@ * @param {Boolean} [options.scaleHeight=true] Whether the height of the * overlay should be adjusted when the zoom changes when using a * {@link OpenSeadragon.Rect} as options.location + * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT] + * How to handle the rotation of the viewport. */ $.Overlay = function(element, location, placement) { @@ -132,13 +151,14 @@ true : options.scaleWidth; this.scaleHeight = options.scaleHeight === undefined ? true : options.scaleHeight; + this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT; }; /** @lends OpenSeadragon.Overlay.prototype */ $.Overlay.prototype = { /** - * Internal function to adjust the position of a PointOverlay + * Internal function to adjust the position of a point-based overlay * depending on it size and anchor. * @function * @param {OpenSeadragon.Point} position @@ -219,6 +239,7 @@ var position = this.position = positionAndSize.position; var size = this.size = positionAndSize.size; + var rotate = positionAndSize.rotate; position = position.apply(Math.round); size = size.apply(Math.round); @@ -239,6 +260,13 @@ style.height = size.y + "px"; } } + if (rotate) { + style.transformOrigin = this._getTransformOrigin(); + style.transform = "rotate(" + rotate + "deg)"; + } else { + style.transformOrigin = ""; + style.transform = ""; + } style.position = "absolute"; if (style.display !== 'none') { @@ -249,27 +277,71 @@ // private _getRectOverlayPositionAndSize: function(viewport) { + var position = viewport.pixelFromPoint( + this.bounds.getTopLeft(), true); + var size = viewport.deltaPixelsFromPointsNoRotate( + this.bounds.getSize(), true); + var rotate = 0; + // BOUNDING_BOX is only valid if both directions get scaled. + // Get replaced by exact otherwise. + if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && + this.scaleWidth && this.scaleHeight) { + var boundingBox = new $.Rect( + position.x, position.y, size.x, size.y, viewport.degrees) + .getBoundingBox(); + position = boundingBox.getTopLeft(); + size = boundingBox.getSize(); + } else if (this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) { + rotate = viewport.degrees; + } return { - position: viewport.pixelFromPoint( - this.bounds.getTopLeft(), true), - size: viewport.deltaPixelsFromPoints( - this.bounds.getSize(), true) + position: position, + size: size, + rotate: rotate }; }, // private _getPointOverlayPositionAndSize: function(viewport) { - var element = this.element; var position = viewport.pixelFromPoint( this.bounds.getTopLeft(), true); - var size = this.checkResize ? $.getElementSize(element) : this.size; + var size = this.checkResize ? + $.getElementSize(this.element) : this.size; this.adjust(position, size); + // For point overlays, BOUNDING_BOX is invalid and get replaced by EXACT. + var rotate = this.rotationMode === $.OverlayRotationMode.NO_ROTATION ? + 0 : viewport.degrees; return { position: position, - size: size + size: size, + rotate: rotate }; }, + // private + _getTransformOrigin: function() { + if (this.scales) { + return "top left"; + } + + var result = ""; + var properties = $.Placement.properties[this.placement]; + if (!properties) { + return result; + } + if (properties.isLeft) { + result = "left"; + } else if (properties.isRight) { + result = "right"; + } + if (properties.isTop) { + result += " top"; + } else if (properties.isBottom) { + result += " bottom"; + } + return result; + }, + /** * Changes the location and placement of the overlay. * @function diff --git a/src/viewer.js b/src/viewer.js index c3054768..49c36e31 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2241,7 +2241,8 @@ function getOverlayObject( viewer, overlay ) { onDraw: overlay.onDraw, checkResize: overlay.checkResize, scaleWidth: overlay.scaleWidth, - scaleHeight: overlay.scaleHeight + scaleHeight: overlay.scaleHeight, + rotationMode: overlay.rotationMode }); } From f6c09ca7165d289317305461e131072db829b4f0 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 28 Mar 2016 17:07:47 -0400 Subject: [PATCH 03/19] Add viewport.viewportToViewerElementRectangle --- src/rectangle.js | 29 ++++++++++++++++++++++++++++- src/viewport.js | 14 ++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/rectangle.js b/src/rectangle.js index cae13e88..afe5da18 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -110,6 +110,33 @@ $.Rect = function(x, y, width, height, degrees) { } }; +/** + * Builds a rectangle having the 3 specified points as summits. + * @static + * @memberof OpenSeadragon.Rect + * @param {OpenSeadragon.Point} topLeft + * @param {OpenSeadragon.Point} topRight + * @param {OpenSeadragon.Point} bottomLeft + * @returns {OpenSeadragon.Rect} + */ +$.Rect.fromSummits = function(topLeft, topRight, bottomLeft) { + var width = topLeft.distanceTo(topRight); + var height = topLeft.distanceTo(bottomLeft); + var diff = topRight.minus(topLeft); + var radians = Math.atan(diff.y / diff.x); + if (diff.x < 0) { + radians += Math.PI; + } else if (diff.y < 0) { + radians += 2 * Math.PI; + } + return new $.Rect( + topLeft.x, + topLeft.y, + width, + height, + radians / Math.PI * 180); +}; + /** @lends OpenSeadragon.Rect.prototype */ $.Rect.prototype = { /** @@ -284,7 +311,7 @@ $.Rect.prototype = { * Rotates a rectangle around a point. * @function * @param {Number} degrees The angle in degrees to rotate. - * @param {OpenSeadragon.Point} pivot The point about which to rotate. + * @param {OpenSeadragon.Point} [pivot] The point about which to rotate. * Defaults to the center of the rectangle. * @return {OpenSeadragon.Rect} */ diff --git a/src/viewport.js b/src/viewport.js index 63b4dcaf..821d8075 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -1262,6 +1262,20 @@ $.Viewport.prototype = { return this.pixelFromPoint( point, true ); }, + /** + * Convert a rectangle in viewport coordinates to pixel coordinates relative + * to the viewer element. + * @param {OpenSeadragon.Rect} rectangle the rectangle to convert + * @returns {OpenSeadragon.Rect} the converted rectangle + */ + viewportToViewerElementRectangle: function(rectangle) { + return $.Rect.fromSummits( + this.pixelFromPoint(rectangle.getTopLeft(), true), + this.pixelFromPoint(rectangle.getTopRight(), true), + this.pixelFromPoint(rectangle.getBottomLeft(), true) + ); + }, + /** * Convert pixel coordinates relative to the window to viewport coordinates. * @param {OpenSeadragon.Point} pixel From 33bd943b7a5bad0f34edcee0982349dff8515e5e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 29 Mar 2016 15:29:36 -0400 Subject: [PATCH 04/19] Set overlays position and size with floating point values. --- src/overlay.js | 3 - test/modules/overlays.js | 121 +++++++++++++++++++++++---------------- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index 7ee7941a..4e7552c6 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -241,9 +241,6 @@ var size = this.size = positionAndSize.size; var rotate = positionAndSize.rotate; - position = position.apply(Math.round); - size = size.apply(Math.round); - // call the onDraw callback if it exists to allow one to overwrite // the drawing/positioning/sizing of the overlay if (this.onDraw) { diff --git a/test/modules/overlays.js b/test/modules/overlays.js index bb317050..077908cb 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -2,6 +2,8 @@ ( function() { var viewer; + // jQuery.position can give results quite different than what set in style.left + var epsilon = 1; module( "Overlays", { setup: function() { @@ -237,30 +239,38 @@ } ] } ); - function checkOverlayPosition( contextMessage ) { + function checkOverlayPosition(contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.imageToViewerElementCoordinates( - new OpenSeadragon.Point( 13, 120 ) ).apply( Math.round ); - var actPosition = $( "#overlay" ).position(); - equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(13, 120)); + var actPosition = $("#overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Y position mismatch " + contextMessage); - var zoom = viewport.viewportToImageZoom( viewport.getZoom( true ) ); - var expectedWidth = Math.round( 124 * zoom ); - var expectedHeight = Math.round( 132 * zoom ); - equal( $( "#overlay" ).width(), expectedWidth, "Width mismatch " + contextMessage ); - equal( $( "#overlay" ).height( ), expectedHeight, "Height mismatch " + contextMessage ); + var zoom = viewport.viewportToImageZoom(viewport.getZoom(true)); + var expectedWidth = 124 * zoom; + var expectedHeight = 132 * zoom; + Util.assessNumericValue($("#overlay").width(), expectedWidth, epsilon, + "Width mismatch " + contextMessage); + Util.assessNumericValue($("#overlay").height(), expectedHeight, epsilon, + "Height mismatch " + contextMessage); expPosition = viewport.imageToViewerElementCoordinates( - new OpenSeadragon.Point( 400, 500 ) ).apply( Math.round ); - actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(400, 500)); + actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); - equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage ); - equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage ); + Util.assessNumericValue($("#fixed-overlay").width(), 70, epsilon, + "Fixed overlay width mismatch " + contextMessage); + Util.assessNumericValue($("#fixed-overlay").height(), 60, epsilon, + "Fixed overlay height mismatch " + contextMessage); } waitForViewer( function() { @@ -305,25 +315,33 @@ var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.round ); + new OpenSeadragon.Point(0.2, 0.1)); var actPosition = $( "#overlay" ).position(); - equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Y position mismatch " + contextMessage); var expectedSize = viewport.deltaPixelsFromPoints( - new OpenSeadragon.Point( 0.5, 0.1 ) ); - equal( $( "#overlay" ).width(), expectedSize.x, "Width mismatch " + contextMessage ); - equal( $( "#overlay" ).height(), expectedSize.y, "Height mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.1)); + Util.assessNumericValue($("#overlay").width(), expectedSize.x, epsilon, + "Width mismatch " + contextMessage); + Util.assessNumericValue($("#overlay").height(), expectedSize.y, epsilon, + "Height mismatch " + contextMessage); expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ).apply( Math.round ); - actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)); + actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); - equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage ); - equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage ); + Util.assessNumericValue($("#fixed-overlay").width(), 70, epsilon, + "Fixed overlay width mismatch " + contextMessage); + Util.assessNumericValue($("#fixed-overlay").height(), 60, epsilon, + "Fixed overlay height mismatch " + contextMessage); } waitForViewer( function() { @@ -373,22 +391,25 @@ var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.round ); - var actPosition = $( "#overlay" ).position(); - equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.2, 0.1)); + var actPosition = $("#overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Y position mismatch " + contextMessage); } function checkFixedOverlayPosition( expectedOffset, contextMessage ) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ) - .apply( Math.round ) - .plus( expectedOffset ); - var actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)) + .plus(expectedOffset); + var actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); } waitForViewer( function() { @@ -448,12 +469,13 @@ var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ) - .apply( Math.round ) - .plus( expectedOffset ); - var actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)) + .plus(expectedOffset); + var actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); } waitForViewer( function() { @@ -502,12 +524,13 @@ var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ) - .apply( Math.round ) - .plus( expectedOffset ); + new OpenSeadragon.Point(0.5, 0.6)) + .plus(expectedOffset); var actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); } waitForViewer( function() { From ffbb8b2cfe6444f7c4427a4c8b5c4a77fd278837 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 30 Mar 2016 11:16:29 -0400 Subject: [PATCH 05/19] Add support of overlays rotation on IE9. --- src/openseadragon.js | 43 +++++++++++++++++++++++++++++++++++++++++++ src/overlay.js | 18 ++++++++++++------ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 07523516..94c7de6a 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1337,6 +1337,49 @@ if (typeof define === 'function' && define.amd) { return window.getComputedStyle( element, "" ); }, + /** + * Returns the property with the correct vendor prefix appended. + * @param {String} property the property name + * @returns {String} the property with the correct prefix or null if not + * supported. + */ + getCssPropertyWithVendorPrefix: function(property) { + var memo = {}; + + $.getCssPropertyWithVendorPrefix = function(property) { + if (memo[property] !== undefined) { + return memo[property]; + } + var style = document.createElement('div').style; + var result = null; + if (style[property] !== undefined) { + result = property; + } else { + var prefixes = ['Webkit', 'Moz', 'MS', 'O', + 'webkit', 'moz', 'ms', 'o']; + var suffix = $.capitalizeFirstLetter(property); + for (var i = 0; i < prefixes.length; i++) { + var prop = prefixes[i] + suffix; + if (style[prop] !== undefined) { + result = prop; + break; + } + } + } + memo[property] = result; + return result; + }; + return $.getCssPropertyWithVendorPrefix(property); + }, + + /** + * Capitalizes the first letter of a string + * @param {String} string + * @returns {String} The string with the first letter capitalized + */ + capitalizeFirstLetter: function(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + }, /** * Determines if a point is within the bounding rectangle of the given element (hit-test). diff --git a/src/overlay.js b/src/overlay.js index 4e7552c6..c8a5ed22 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -257,12 +257,18 @@ style.height = size.y + "px"; } } - if (rotate) { - style.transformOrigin = this._getTransformOrigin(); - style.transform = "rotate(" + rotate + "deg)"; - } else { - style.transformOrigin = ""; - style.transform = ""; + var transformOriginProp = $.getCssPropertyWithVendorPrefix( + 'transformOrigin'); + var transformProp = $.getCssPropertyWithVendorPrefix( + 'transform'); + if (transformOriginProp && transformProp) { + if (rotate) { + style[transformOriginProp] = this._getTransformOrigin(); + style[transformProp] = "rotate(" + rotate + "deg)"; + } else { + style[transformOriginProp] = ""; + style[transformProp] = ""; + } } style.position = "absolute"; From 577327a6291e762dafc615cceb7bb9364d86a91a Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 30 Mar 2016 15:12:50 -0400 Subject: [PATCH 06/19] Change overlays to now always having Point location. --- src/overlay.js | 220 +++++++++++++++++++++++------------------ src/viewer.js | 4 +- test/demo/overlay.html | 30 ++++-- 3 files changed, 147 insertions(+), 107 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index c8a5ed22..e9d2d299 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -81,23 +81,23 @@ * @param {Element} options.element * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The * location of the overlay on the image. If a {@link OpenSeadragon.Point} - * is specified, the overlay will keep a constant size independently of the - * zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will - * be adjusted when the zoom changes. + * is specified, the overlay will be located at this location with respect + * to the placement option. If a {@link OpenSeadragon.Rect} is specified, + * the overlay will be placed at this location with the corresponding width + * and height and placement TOP_LEFT. * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT] - * Relative position to the viewport. - * Only used if location is a {@link OpenSeadragon.Point}. + * Defines what part of the overlay should be at the specified options.location * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw] * @param {Boolean} [options.checkResize=true] Set to false to avoid to - * check the size of the overlay everytime it is drawn when using a - * {@link OpenSeadragon.Point} as options.location. It will improve - * performances but will cause a misalignment if the overlay size changes. - * @param {Boolean} [options.scaleWidth=true] Whether the width of the - * overlay should be adjusted when the zoom changes when using a - * {@link OpenSeadragon.Rect} as options.location - * @param {Boolean} [options.scaleHeight=true] Whether the height of the - * overlay should be adjusted when the zoom changes when using a - * {@link OpenSeadragon.Rect} as options.location + * check the size of the overlay everytime it is drawn in the directions + * which are not scaled. It will improve performances but will cause a + * misalignment if the overlay size changes. + * @param {Number} [options.width] The width of the overlay in viewport + * coordinates. If specified, the width of the overlay will be adjusted when + * the zoom changes. + * @param {Number} [options.height] The height of the overlay in viewport + * coordinates. If specified, the height of the overlay will be adjusted when + * the zoom changes. * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT] * How to handle the rotation of the viewport. */ @@ -124,42 +124,37 @@ }; } - this.element = options.element; - this.scales = options.location instanceof $.Rect; - this.bounds = new $.Rect( - options.location.x, - options.location.y, - options.location.width, - options.location.height); - - // this.position is never read by this class but is kept for backward - // compatibility - this.position = this.bounds.getTopLeft(); - - // this.size is only used by PointOverlay with options.checkResize === false - this.size = this.bounds.getSize(); - + this.element = options.element; this.style = options.element.style; - - // rects are always top-left (RectOverlays don't use placement) - this.placement = options.location instanceof $.Point ? - options.placement : $.Placement.TOP_LEFT; - this.onDraw = options.onDraw; - this.checkResize = options.checkResize === undefined ? - true : options.checkResize; - this.scaleWidth = options.scaleWidth === undefined ? - true : options.scaleWidth; - this.scaleHeight = options.scaleHeight === undefined ? - true : options.scaleHeight; - this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT; + this._init(options); }; /** @lends OpenSeadragon.Overlay.prototype */ $.Overlay.prototype = { + // private + _init: function(options) { + this.location = options.location; + this.placement = options.placement === undefined ? + $.Placement.TOP_LEFT : options.placement; + this.onDraw = options.onDraw; + this.checkResize = options.checkResize === undefined ? + true : options.checkResize; + this.width = options.width === undefined ? null : options.width; + this.height = options.height === undefined ? null : options.height; + this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT; + + if (this.location instanceof $.Rect) { + this.width = this.location.width; + this.height = this.location.height; + this.location = this.location.getTopLeft(); + this.placement = $.Placement.TOP_LEFT; + } + }, + /** - * Internal function to adjust the position of a point-based overlay - * depending on it size and anchor. + * Internal function to adjust the position of an overlay + * depending on it size and placement. * @function * @param {OpenSeadragon.Point} position * @param {OpenSeadragon.Point} size @@ -209,13 +204,19 @@ style.left = ""; style.position = ""; - if (this.scales) { - if (this.scaleWidth) { - style.width = ""; - } - if (this.scaleHeight) { - style.height = ""; - } + if (this.width !== null) { + style.width = ""; + } + if (this.height !== null) { + style.height = ""; + } + var transformOriginProp = $.getCssPropertyWithVendorPrefix( + 'transformOrigin'); + var transformProp = $.getCssPropertyWithVendorPrefix( + 'transform'); + if (transformOriginProp && transformProp) { + style[transformOriginProp] = ""; + style[transformProp] = ""; } }, @@ -233,11 +234,9 @@ this.size = $.getElementSize(element); } - var positionAndSize = this.scales ? - this._getRectOverlayPositionAndSize(viewport) : - this._getPointOverlayPositionAndSize(viewport); + var positionAndSize = this._getOverlayPositionAndSize(viewport); - var position = this.position = positionAndSize.position; + var position = positionAndSize.position; var size = this.size = positionAndSize.size; var rotate = positionAndSize.rotate; @@ -249,13 +248,11 @@ var style = this.style; style.left = position.x + "px"; style.top = position.y + "px"; - if (this.scales) { - if (this.scaleWidth) { - style.width = size.x + "px"; - } - if (this.scaleHeight) { - style.height = size.y + "px"; - } + if (this.width !== null) { + style.width = size.x + "px"; + } + if (this.height !== null) { + style.height = size.y + "px"; } var transformOriginProp = $.getCssPropertyWithVendorPrefix( 'transformOrigin'); @@ -279,16 +276,38 @@ }, // private - _getRectOverlayPositionAndSize: function(viewport) { - var position = viewport.pixelFromPoint( - this.bounds.getTopLeft(), true); - var size = viewport.deltaPixelsFromPointsNoRotate( - this.bounds.getSize(), true); + _getOverlayPositionAndSize: function(viewport) { + var position = viewport.pixelFromPoint(this.location, true); + var width = this.size.x; + var height = this.size.y; + if (this.width !== null || this.height !== null) { + var scaledSize = viewport.deltaPixelsFromPointsNoRotate( + new $.Point(this.width || 0, this.height || 0), true); + if (this.width !== null) { + width = scaledSize.x; + } + if (this.height !== null) { + height = scaledSize.y; + } + } + if (this.checkResize && + (this.width === null || this.height === null)) { + var eltSize = this.size = $.getElementSize(this.element); + if (this.width === null) { + width = eltSize.x; + } + if (this.height === null) { + height = eltSize.y; + } + } + var size = new $.Point(width, height); + this.adjust(position, size); + var rotate = 0; // BOUNDING_BOX is only valid if both directions get scaled. - // Get replaced by exact otherwise. + // Get replaced by EXACT otherwise. if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && - this.scaleWidth && this.scaleHeight) { + this.width !== null && this.height !== null) { var boundingBox = new $.Rect( position.x, position.y, size.x, size.y, viewport.degrees) .getBoundingBox(); @@ -297,23 +316,7 @@ } else if (this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) { rotate = viewport.degrees; } - return { - position: position, - size: size, - rotate: rotate - }; - }, - // private - _getPointOverlayPositionAndSize: function(viewport) { - var position = viewport.pixelFromPoint( - this.bounds.getTopLeft(), true); - var size = this.checkResize ? - $.getElementSize(this.element) : this.size; - this.adjust(position, size); - // For point overlays, BOUNDING_BOX is invalid and get replaced by EXACT. - var rotate = this.rotationMode === $.OverlayRotationMode.NO_ROTATION ? - 0 : viewport.degrees; return { position: position, size: size, @@ -346,29 +349,52 @@ }, /** - * Changes the location and placement of the overlay. + * Changes the overlay settings. * @function - * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location + * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location + * If an object is specified, the options are the same than the constructor + * except for the element which can not be changed. * @param {OpenSeadragon.Placement} position */ update: function(location, placement) { - this.scales = location instanceof $.Rect; - this.bounds = new $.Rect( - location.x, - location.y, - location.width, - location.height); - // rects are always top-left - this.placement = location instanceof $.Point ? - placement : $.Placement.TOP_LEFT; + var options = $.isPlainObject(location) ? location : { + location: location, + placement: placement + }; + this._init({ + location: options.location || this.location, + placement: options.placement !== undefined ? + options.placement : this.placement, + onDraw: options.onDraw || this.onDraw, + checkResize: options.checkResize || this.checkResize, + width: options.width !== undefined ? options.width : this.width, + height: options.height !== undefined ? options.height : this.height, + rotationMode: options.rotationMode || this.rotationMode + }); }, /** + * Returns the current bounds of the overlay in viewport coordinates * @function + * @param {OpenSeadragon.Viewport} [viewport] the viewport * @returns {OpenSeadragon.Rect} overlay bounds */ - getBounds: function() { - return this.bounds.clone(); + getBounds: function(viewport) { + var width = this.width; + var height = this.height; + if (width === null || height === null) { + $.console.assert(viewport, 'The viewport must be specified to' + + ' get the bounds of a not entirely scaling overlay'); + var size = viewport.deltaPointsFromPixels(this.size, true); + if (width === null) { + width = size.x; + } + if (height === null) { + height = size.y; + } + } + return new $.Rect( + this.location.x, this.location.y, width, height); } }; diff --git a/src/viewer.js b/src/viewer.js index 49c36e31..c1e34119 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2240,8 +2240,8 @@ function getOverlayObject( viewer, overlay ) { placement: placement, onDraw: overlay.onDraw, checkResize: overlay.checkResize, - scaleWidth: overlay.scaleWidth, - scaleHeight: overlay.scaleHeight, + width: overlay.width, + height: overlay.height, rotationMode: overlay.rotationMode }); } diff --git a/test/demo/overlay.html b/test/demo/overlay.html index d2dde513..57435014 100644 --- a/test/demo/overlay.html +++ b/test/demo/overlay.html @@ -15,6 +15,8 @@
+ + 0deg
From 70b39d681b5a73be170a79c9720a1113f2988159 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 31 Mar 2016 13:25:59 -0400 Subject: [PATCH 07/19] Fix viewer.addOverlay and Overlay.getBounds --- src/overlay.js | 9 +- src/viewer.js | 45 ++-- test/modules/overlays.js | 561 +++++++++++++++++++++++++-------------- 3 files changed, 382 insertions(+), 233 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index e9d2d299..c59219f4 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -383,9 +383,9 @@ var width = this.width; var height = this.height; if (width === null || height === null) { - $.console.assert(viewport, 'The viewport must be specified to' + + $.console.assert(!viewport, 'The viewport must be specified to' + ' get the bounds of a not entirely scaling overlay'); - var size = viewport.deltaPointsFromPixels(this.size, true); + var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true); if (width === null) { width = size.x; } @@ -393,8 +393,9 @@ height = size.y; } } - return new $.Rect( - this.location.x, this.location.y, width, height); + var location = this.location.clone(); + this.adjust(location, new $.Point(width, height)); + return new $.Rect(location.x, location.y, width, height); } }; diff --git a/src/viewer.js b/src/viewer.js index c1e34119..b2d6e5f2 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2201,32 +2201,23 @@ function getOverlayObject( viewer, overlay ) { } var location = overlay.location; - if ( !location ) { - if ( overlay.width && overlay.height ) { - location = overlay.px !== undefined ? - viewer.viewport.imageToViewportRectangle( new $.Rect( - overlay.px, - overlay.py, - overlay.width, - overlay.height - ) ) : - new $.Rect( - overlay.x, - overlay.y, - overlay.width, - overlay.height - ); - } else { - location = overlay.px !== undefined ? - viewer.viewport.imageToViewportCoordinates( new $.Point( - overlay.px, - overlay.py - ) ) : - new $.Point( - overlay.x, - overlay.y - ); + var width = overlay.width; + var height = overlay.height; + if (!location) { + var x = overlay.x; + var y = overlay.y; + if (overlay.px !== undefined) { + var rect = viewer.viewport.imageToViewportRectangle(new $.Rect( + overlay.px, + overlay.py, + width || 0, + height || 0)); + x = rect.x; + y = rect.y; + width = width !== undefined ? rect.width : undefined; + height = height !== undefined ? rect.height : undefined; } + location = new $.Point(x, y); } var placement = overlay.placement; @@ -2240,8 +2231,8 @@ function getOverlayObject( viewer, overlay ) { placement: placement, onDraw: overlay.onDraw, checkResize: overlay.checkResize, - width: overlay.width, - height: overlay.height, + width: width, + height: height, rotationMode: overlay.rotationMode }); } diff --git a/test/modules/overlays.js b/test/modules/overlays.js index 077908cb..496c4f89 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -1,111 +1,111 @@ /* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal, testLog */ -( function() { +(function() { var viewer; // jQuery.position can give results quite different than what set in style.left var epsilon = 1; - module( "Overlays", { + module("Overlays", { setup: function() { - var example = $( '
' ).appendTo( "#qunit-fixture" ); - var fixedOverlay = $( '
' ).appendTo( example ); - fixedOverlay.width( 70 ); - fixedOverlay.height( 60 ); + var example = $('
').appendTo("#qunit-fixture"); + var fixedOverlay = $('
').appendTo(example); + fixedOverlay.width(70); + fixedOverlay.height(60); testLog.reset(); }, teardown: function() { resetTestVariables(); } - } ); + }); var resetTestVariables = function() { - if ( viewer ) { + if (viewer) { viewer.close(); } }; - function waitForViewer( handler, count ) { - if ( typeof count !== "number" ) { + function waitForViewer(handler, count) { + if (typeof count !== "number") { count = 0; } var ready = viewer.isOpen() && viewer.drawer !== null && !viewer.world.needsDraw() && - Util.equalsWithVariance( viewer.viewport.getBounds( true ).x, - viewer.viewport.getBounds().x, 0.000 ) && - Util.equalsWithVariance( viewer.viewport.getBounds( true ).y, - viewer.viewport.getBounds().y, 0.000 ) && - Util.equalsWithVariance( viewer.viewport.getBounds( true ).width, - viewer.viewport.getBounds().width, 0.000 ); + Util.equalsWithVariance(viewer.viewport.getBounds(true).x, + viewer.viewport.getBounds().x, 0.000) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).y, + viewer.viewport.getBounds().y, 0.000) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).width, + viewer.viewport.getBounds().width, 0.000); - if ( ready ) { + if (ready) { handler(); - } else if ( count < 50 ) { + } else if (count < 50) { count++; - setTimeout( function() { - waitForViewer( handler, count ); - }, 100 ); + setTimeout(function() { + waitForViewer(handler, count); + }, 100); } else { - console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + - ":" + viewer.world.needsDraw() ); + console.log("waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + + ":" + viewer.world.needsDraw()); handler(); } } - asyncTest( 'Overlays via viewer options', function() { + asyncTest('Overlays via viewer options', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', - tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'], springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: 0.1, y: 0.4, width: 0.09, height: 0.09, id: "overlay" - } ] - } ); - viewer.addHandler( 'open', openHandler ); + }] + }); + viewer.addHandler('open', openHandler); function openHandler() { - viewer.removeHandler( 'open', openHandler ); + viewer.removeHandler('open', openHandler); - equal( viewer.overlays.length, 1, "Global overlay should be added." ); - equal( viewer.currentOverlays.length, 1, "Global overlay should be open." ); + equal(viewer.overlays.length, 1, "Global overlay should be added."); + equal(viewer.currentOverlays.length, 1, "Global overlay should be open."); - viewer.addHandler( 'open', openPageHandler ); - viewer.goToPage( 1 ); + viewer.addHandler('open', openPageHandler); + viewer.goToPage(1); } function openPageHandler() { - viewer.removeHandler( 'open', openPageHandler ); + viewer.removeHandler('open', openPageHandler); - equal( viewer.overlays.length, 1, "Global overlay should stay after page switch." ); - equal( viewer.currentOverlays.length, 1, "Global overlay should re-open after page switch." ); + equal(viewer.overlays.length, 1, "Global overlay should stay after page switch."); + equal(viewer.currentOverlays.length, 1, "Global overlay should re-open after page switch."); - viewer.addHandler( 'close', closeHandler ); + viewer.addHandler('close', closeHandler); viewer.close(); } function closeHandler() { - viewer.removeHandler( 'close', closeHandler ); + viewer.removeHandler('close', closeHandler); - equal( viewer.overlays.length, 1, "Global overlay should not be removed on close." ); - equal( viewer.currentOverlays.length, 0, "Global overlay should be closed on close." ); + equal(viewer.overlays.length, 1, "Global overlay should not be removed on close."); + equal(viewer.currentOverlays.length, 0, "Global overlay should be closed on close."); start(); } - } ); + }); - asyncTest( 'Page Overlays via viewer options', function() { + asyncTest('Page Overlays via viewer options', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', - tileSources: [ { + tileSources: [{ Image: { xmlns: "http://schemas.microsoft.com/deepzoom/2008", Url: "/test/data/testpattern_files/", @@ -117,13 +117,13 @@ Height: 1000 } }, - overlays: [ { + overlays: [{ x: 0.1, y: 0.4, width: 0.09, height: 0.09, id: "overlay" - } ] + }] }, { Image: { xmlns: "http://schemas.microsoft.com/deepzoom/2008", @@ -136,96 +136,96 @@ Height: 1000 } } - } ], + }], springStiffness: 100 // Faster animation = faster tests - } ); - viewer.addHandler( 'open', openHandler ); + }); + viewer.addHandler('open', openHandler); function openHandler() { - viewer.removeHandler( 'open', openHandler ); + viewer.removeHandler('open', openHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added." ); - equal( viewer.currentOverlays.length, 1, "Page overlay should be open." ); + equal(viewer.overlays.length, 0, "No global overlay should be added."); + equal(viewer.currentOverlays.length, 1, "Page overlay should be open."); - viewer.addHandler( 'open', openPageHandler ); - viewer.goToPage( 1 ); + viewer.addHandler('open', openPageHandler); + viewer.goToPage(1); } function openPageHandler() { - viewer.removeHandler( 'open', openPageHandler ); + viewer.removeHandler('open', openPageHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added after page switch." ); - equal( viewer.currentOverlays.length, 0, "No page overlay should be opened after page switch." ); + equal(viewer.overlays.length, 0, "No global overlay should be added after page switch."); + equal(viewer.currentOverlays.length, 0, "No page overlay should be opened after page switch."); - viewer.addHandler( 'close', closeHandler ); + viewer.addHandler('close', closeHandler); viewer.close(); } function closeHandler() { - viewer.removeHandler( 'close', closeHandler ); + viewer.removeHandler('close', closeHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added on close." ); - equal( viewer.currentOverlays.length, 0, "Page overlay should be closed on close." ); + equal(viewer.overlays.length, 0, "No global overlay should be added on close."); + equal(viewer.currentOverlays.length, 0, "Page overlay should be closed on close."); start(); } - } ); + }); - asyncTest( 'Overlays via addOverlay method', function() { + asyncTest('Overlays via addOverlay method', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', - tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'], springStiffness: 100 // Faster animation = faster tests - } ); - viewer.addHandler( 'open', openHandler ); + }); + viewer.addHandler('open', openHandler); function openHandler() { - viewer.removeHandler( 'open', openHandler ); + viewer.removeHandler('open', openHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added." ); - equal( viewer.currentOverlays.length, 0, "No overlay should be open." ); + equal(viewer.overlays.length, 0, "No global overlay should be added."); + equal(viewer.currentOverlays.length, 0, "No overlay should be open."); - var rect = new OpenSeadragon.Rect( 0.1, 0.1, 0.1, 0.1 ); - var overlay = $( "
" ).prop( "id", "overlay" ).get( 0 ); - viewer.addOverlay( overlay, rect ); - equal( viewer.overlays.length, 0, "No manual overlay should be added as global overlay." ); - equal( viewer.currentOverlays.length, 1, "A manual overlay should be open." ); + var rect = new OpenSeadragon.Rect(0.1, 0.1, 0.1, 0.1); + var overlay = $("
").prop("id", "overlay").get(0); + viewer.addOverlay(overlay, rect); + equal(viewer.overlays.length, 0, "No manual overlay should be added as global overlay."); + equal(viewer.currentOverlays.length, 1, "A manual overlay should be open."); - viewer.addHandler( 'open', openPageHandler ); - viewer.goToPage( 1 ); + viewer.addHandler('open', openPageHandler); + viewer.goToPage(1); } function openPageHandler() { - viewer.removeHandler( 'open', openPageHandler ); + viewer.removeHandler('open', openPageHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added after page switch." ); - equal( viewer.currentOverlays.length, 0, "Manual overlay should be removed after page switch." ); + equal(viewer.overlays.length, 0, "No global overlay should be added after page switch."); + equal(viewer.currentOverlays.length, 0, "Manual overlay should be removed after page switch."); - viewer.addHandler( 'close', closeHandler ); + viewer.addHandler('close', closeHandler); viewer.close(); } function closeHandler() { - viewer.removeHandler( 'close', closeHandler ); + viewer.removeHandler('close', closeHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added on close." ); - equal( viewer.currentOverlays.length, 0, "Manual overlay should be removed on close." ); + equal(viewer.overlays.length, 0, "No global overlay should be added on close."); + equal(viewer.currentOverlays.length, 0, "Manual overlay should be removed on close."); start(); } - } ); + }); - asyncTest( 'Overlays size in pixels', function() { + asyncTest('Overlays size in pixels', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ px: 13, py: 120, width: 124, @@ -236,8 +236,8 @@ py: 500, id: "fixed-overlay", placement: "TOP_LEFT" - } ] - } ); + }] + }); function checkOverlayPosition(contextMessage) { var viewport = viewer.viewport; @@ -273,31 +273,31 @@ "Fixed overlay height mismatch " + contextMessage); } - waitForViewer( function() { - checkOverlayPosition( "after opening using image coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after opening using image coordinates"); - viewer.viewport.zoomBy( 1.1 ).panBy( new OpenSeadragon.Point( 0.1, 0.2 ) ); - waitForViewer( function() { - checkOverlayPosition( "after zoom and pan using image coordinates" ); + viewer.viewport.zoomBy(1.1).panBy(new OpenSeadragon.Point(0.1, 0.2)); + waitForViewer(function() { + checkOverlayPosition("after zoom and pan using image coordinates"); viewer.viewport.goHome(); - waitForViewer( function() { - checkOverlayPosition( "after goHome using image coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after goHome using image coordinates"); start(); - } ); - } ); + }); + }); - } ); - } ); + }); + }); - asyncTest( 'Overlays size in points', function() { + asyncTest('Overlays size in points', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: 0.2, y: 0.1, width: 0.5, @@ -308,15 +308,15 @@ y: 0.6, id: "fixed-overlay", placement: "TOP_LEFT" - } ] - } ); + }] + }); - function checkOverlayPosition( contextMessage ) { + function checkOverlayPosition(contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( new OpenSeadragon.Point(0.2, 0.1)); - var actPosition = $( "#overlay" ).position(); + var actPosition = $("#overlay").position(); Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, "X position mismatch " + contextMessage); Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, @@ -344,34 +344,34 @@ "Fixed overlay height mismatch " + contextMessage); } - waitForViewer( function() { - checkOverlayPosition( "after opening using viewport coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after opening using viewport coordinates"); - viewer.viewport.zoomBy( 1.1 ).panBy( new OpenSeadragon.Point( 0.1, 0.2 ) ); - waitForViewer( function() { - checkOverlayPosition( "after zoom and pan using viewport coordinates" ); + viewer.viewport.zoomBy(1.1).panBy(new OpenSeadragon.Point(0.1, 0.2)); + waitForViewer(function() { + checkOverlayPosition("after zoom and pan using viewport coordinates"); viewer.viewport.goHome(); - waitForViewer( function() { - checkOverlayPosition( "after goHome using viewport coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after goHome using viewport coordinates"); start(); - } ); - } ); + }); + }); - } ); - } ); + }); + }); - asyncTest( 'Overlays placement', function() { + asyncTest('Overlays placement', function() { - var scalableOverlayLocation = new OpenSeadragon.Rect( 0.2, 0.1, 0.5, 0.1 ); - var fixedOverlayLocation = new OpenSeadragon.Point( 0.5, 0.6 ); + var scalableOverlayLocation = new OpenSeadragon.Rect(0.2, 0.1, 0.5, 0.1); + var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: scalableOverlayLocation.x, y: scalableOverlayLocation.y, width: scalableOverlayLocation.width, @@ -383,11 +383,11 @@ y: fixedOverlayLocation.y, id: "fixed-overlay", placement: "TOP_LEFT" - } ] - } ); + }] + }); // Scalable overlays are always TOP_LEFT - function checkScalableOverlayPosition( contextMessage ) { + function checkScalableOverlayPosition(contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( @@ -399,7 +399,7 @@ "Y position mismatch " + contextMessage); } - function checkFixedOverlayPosition( expectedOffset, contextMessage ) { + function checkFixedOverlayPosition(expectedOffset, contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( @@ -412,60 +412,60 @@ "Fixed overlay Y position mismatch " + contextMessage); } - waitForViewer( function() { + waitForViewer(function() { - checkScalableOverlayPosition( "with TOP_LEFT placement." ); - checkFixedOverlayPosition( new OpenSeadragon.Point( 0, 0 ), - "with TOP_LEFT placement." ); + checkScalableOverlayPosition("with TOP_LEFT placement."); + checkFixedOverlayPosition(new OpenSeadragon.Point(0, 0), + "with TOP_LEFT placement."); // Check that legacy OpenSeadragon.OverlayPlacement is still working - viewer.updateOverlay( "overlay", scalableOverlayLocation, - OpenSeadragon.OverlayPlacement.CENTER ); - viewer.updateOverlay( "fixed-overlay", fixedOverlayLocation, - OpenSeadragon.OverlayPlacement.CENTER ); + viewer.updateOverlay("overlay", scalableOverlayLocation, + OpenSeadragon.OverlayPlacement.CENTER); + viewer.updateOverlay("fixed-overlay", fixedOverlayLocation, + OpenSeadragon.OverlayPlacement.CENTER); - setTimeout( function() { - checkScalableOverlayPosition( "with CENTER placement." ); - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with CENTER placement." ); + setTimeout(function() { + checkScalableOverlayPosition("with CENTER placement."); + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with CENTER placement."); // Check that new OpenSeadragon.Placement is working - viewer.updateOverlay( "overlay", scalableOverlayLocation, - OpenSeadragon.Placement.BOTTOM_RIGHT ); - viewer.updateOverlay( "fixed-overlay", fixedOverlayLocation, - OpenSeadragon.Placement.BOTTOM_RIGHT ); - setTimeout( function() { - checkScalableOverlayPosition( "with BOTTOM_RIGHT placement." ); - checkFixedOverlayPosition( new OpenSeadragon.Point( -70, -60 ), - "with BOTTOM_RIGHT placement." ); + viewer.updateOverlay("overlay", scalableOverlayLocation, + OpenSeadragon.Placement.BOTTOM_RIGHT); + viewer.updateOverlay("fixed-overlay", fixedOverlayLocation, + OpenSeadragon.Placement.BOTTOM_RIGHT); + setTimeout(function() { + checkScalableOverlayPosition("with BOTTOM_RIGHT placement."); + checkFixedOverlayPosition(new OpenSeadragon.Point(-70, -60), + "with BOTTOM_RIGHT placement."); start(); - }, 100 ); + }, 100); - }, 100 ); + }, 100); - } ); - } ); + }); + }); - asyncTest( 'Overlays placement and resizing check', function() { + asyncTest('Overlays placement and resizing check', function() { - var fixedOverlayLocation = new OpenSeadragon.Point( 0.5, 0.6 ); + var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: fixedOverlayLocation.x, y: fixedOverlayLocation.y, id: "fixed-overlay", placement: "CENTER", checkResize: true - } ] - } ); + }] + }); - function checkFixedOverlayPosition( expectedOffset, contextMessage ) { + function checkFixedOverlayPosition(expectedOffset, contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( @@ -478,114 +478,271 @@ "Fixed overlay Y position mismatch " + contextMessage); } - waitForViewer( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with overlay of size 70,60." ); + waitForViewer(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with overlay of size 70,60."); - $( "#fixed-overlay" ).width( 50 ); - $( "#fixed-overlay" ).height( 40 ); + $("#fixed-overlay").width(50); + $("#fixed-overlay").height(40); // The resizing of the overlays is not detected by the viewer's loop. viewer.forceRedraw(); - setTimeout( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -25, -20 ), - "with overlay of size 50,40." ); + setTimeout(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-25, -20), + "with overlay of size 50,40."); // Restore original size - $( "#fixed-overlay" ).width( 70 ); - $( "#fixed-overlay" ).height( 60 ); + $("#fixed-overlay").width(70); + $("#fixed-overlay").height(60); start(); - }, 100 ); - } ); + }, 100); + }); - } ); + }); - asyncTest( 'Overlays placement and no resizing check', function() { + asyncTest('Overlays placement and no resizing check', function() { - var fixedOverlayLocation = new OpenSeadragon.Point( 0.5, 0.6 ); + var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: fixedOverlayLocation.x, y: fixedOverlayLocation.y, id: "fixed-overlay", placement: "CENTER", checkResize: false - } ] - } ); + }] + }); - function checkFixedOverlayPosition( expectedOffset, contextMessage ) { + function checkFixedOverlayPosition(expectedOffset, contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( new OpenSeadragon.Point(0.5, 0.6)) .plus(expectedOffset); - var actPosition = $( "#fixed-overlay" ).position(); + var actPosition = $("#fixed-overlay").position(); Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, "Fixed overlay X position mismatch " + contextMessage); Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, "Fixed overlay Y position mismatch " + contextMessage); } - waitForViewer( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with overlay of size 70,60." ); + waitForViewer(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with overlay of size 70,60."); - $( "#fixed-overlay" ).width( 50 ); - $( "#fixed-overlay" ).height( 40 ); + $("#fixed-overlay").width(50); + $("#fixed-overlay").height(40); // The resizing of the overlays is not detected by the viewer's loop. viewer.forceRedraw(); - setTimeout( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with overlay of size 50,40." ); + setTimeout(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with overlay of size 50,40."); // Restore original size - $( "#fixed-overlay" ).width( 70 ); - $( "#fixed-overlay" ).height( 60 ); + $("#fixed-overlay").width(70); + $("#fixed-overlay").height(60); start(); - }, 100 ); - } ); + }, 100); + }); - } ); + }); // ---------- asyncTest('overlays appear immediately', function() { equal($('#immediate-overlay0').length, 0, 'overlay 0 does not exist'); equal($('#immediate-overlay1').length, 0, 'overlay 1 does not exist'); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: 0, y: 0, id: "immediate-overlay0" - } ] - } ); + }] + }); viewer.addHandler('open', function() { equal($('#immediate-overlay0').length, 1, 'overlay 0 exists'); - viewer.addOverlay( { + viewer.addOverlay({ x: 0, y: 0, id: "immediate-overlay1" - } ); + }); equal($('#immediate-overlay1').length, 1, 'overlay 1 exists'); start(); }); }); -} )( ); + // ---------- + asyncTest('Overlay scaled horizontally only', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100 // Faster animation = faster tests + }); + + viewer.addHandler('open', function() { + viewer.addOverlay({ + id: "horizontally-scaled-overlay", + x: 0, + y: 0, + width: 1 + }); + + var width = $("#horizontally-scaled-overlay").width(); + var height = 100; + var zoom = 1.1; + $("#horizontally-scaled-overlay").get(0).style.height = height + "px"; + + viewer.viewport.zoomBy(zoom); + + waitForViewer(function() { + var newWidth = $("#horizontally-scaled-overlay").width(); + var newHeight = $("#horizontally-scaled-overlay").height(); + equal(newWidth, width * zoom, "Width should be scaled."); + equal(newHeight, height, "Height should not be scaled."); + + start(); + }); + }); + }); + + // ---------- + asyncTest('Overlay scaled vertically only', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100 // Faster animation = faster tests + }); + + viewer.addHandler('open', function() { + viewer.addOverlay({ + id: "vertically-scaled-overlay", + x: 0, + y: 0, + height: 1 + }); + + var width = 100; + var height = $("#vertically-scaled-overlay").height(); + var zoom = 1.1; + $("#vertically-scaled-overlay").get(0).style.width = width + "px"; + + viewer.viewport.zoomBy(zoom); + + waitForViewer(function() { + var newWidth = $("#vertically-scaled-overlay").width(); + var newHeight = $("#vertically-scaled-overlay").height(); + equal(newWidth, width, "Width should not be scaled."); + equal(newHeight, height * zoom, "Height should be scaled."); + + start(); + }); + }); + }); + + asyncTest('Overlay.getBounds', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100 // Faster animation = faster tests + }); + + viewer.addHandler('open', function() { + viewer.addOverlay({ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT + }); + viewer.addOverlay({ + id: "horizontally-scaled-overlay", + x: 0.5, + y: 0.5, + width: 1, + placement: OpenSeadragon.Placement.CENTER + }); + viewer.addOverlay({ + id: "vertically-scaled-overlay", + x: 0, + y: 0.5, + height: 1, + placement: OpenSeadragon.Placement.LEFT + }); + viewer.addOverlay({ + id: "not-scaled-overlay", + x: 1, + y: 0, + placement: OpenSeadragon.Placement.TOP_RIGHT + }); + + var notScaledWidth = 100; + var notScaledHeight = 100; + $("#horizontally-scaled-overlay").get(0).style.height = notScaledHeight + "px"; + $("#vertically-scaled-overlay").get(0).style.width = notScaledWidth + "px"; + $("#not-scaled-overlay").get(0).style.width = notScaledWidth + "px"; + $("#not-scaled-overlay").get(0).style.height = notScaledHeight + "px"; + + var notScaledSize = viewer.viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(); + var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1); + ok(expectedBounds.equals(actualBounds), + "The fully scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + + actualBounds = viewer.getOverlayById("horizontally-scaled-overlay") + .getBounds(viewer.viewport); + expectedBounds = new OpenSeadragon.Rect( + 0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y); + ok(expectedBounds.equals(actualBounds), + "The horizontally scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + actualBounds = viewer.getOverlayById("vertically-scaled-overlay") + .getBounds(viewer.viewport); + expectedBounds = new OpenSeadragon.Rect( + 0, 0, notScaledSize.x, 1); + ok(expectedBounds.equals(actualBounds), + "The vertically scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + actualBounds = viewer.getOverlayById("not-scaled-overlay") + .getBounds(viewer.viewport); + expectedBounds = new OpenSeadragon.Rect( + 1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y); + ok(expectedBounds.equals(actualBounds), + "The not scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + start(); + }); + }); + +})(); From 15a0db045e0b6a991c49c20cf87a467424e17b17 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 31 Mar 2016 15:45:44 -0400 Subject: [PATCH 08/19] Fix changelog and add comments. --- changelog.txt | 4 +++- src/overlay.js | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 744d09b2..962118f8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,7 @@ OPENSEADRAGON CHANGELOG * BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and Viewport.contentAspectY have been removed. (#846) +* BREAKING CHANGE: Overlay.scales, Overlay.bounds and Overlay.position have been removed. (#896) * DEPRECATION: Viewport.setHomeBounds has been deprecated (#846) * DEPRECATION: the Viewport constructor is now ignoring the contentSize option (#846) * Tile edge smoothing at high zoom (#764) @@ -30,7 +31,8 @@ OPENSEADRAGON CHANGELOG * Fixed issue causing HTML pages to jump unwantedly to the reference strip upon loading (#872) * Added addOnceHandler method to EventSource (#887) * Added TiledImage.fitBounds method (#888) -* Added scaledWidth and scaleHeight options to Rect overlays to allow to scale in only one dimension. +* Overlays can now be scaled in only one dimension by providing a point location and either width or height (#896) +* Added full rotation support to overlays (#729, #193) 2.1.0: diff --git a/src/overlay.js b/src/overlay.js index c59219f4..0f276dd5 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -140,10 +140,16 @@ this.onDraw = options.onDraw; this.checkResize = options.checkResize === undefined ? true : options.checkResize; + + // When this.width is not null, the overlay get scaled horizontally this.width = options.width === undefined ? null : options.width; + + // When this.height is not null, the overlay get scaled vertically this.height = options.height === undefined ? null : options.height; + this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT; + // Having a rect as location is a syntactic sugar if (this.location instanceof $.Rect) { this.width = this.location.width; this.height = this.location.height; @@ -231,6 +237,9 @@ element.prevElementParent = element.parentNode; element.prevNextSibling = element.nextSibling; container.appendChild(element); + + // this.size is used by overlays which don't get scaled in at + // least one direction when this.checkResize is set to false. this.size = $.getElementSize(element); } From 05a7e5e46708df75c99cb6a49518dbbc38183685 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 31 Mar 2016 16:53:19 -0400 Subject: [PATCH 09/19] Fix bounding box rotation mode with placement other than top left. --- src/overlay.js | 70 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index 0f276dd5..5be33b8f 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -287,6 +287,34 @@ // private _getOverlayPositionAndSize: function(viewport) { var position = viewport.pixelFromPoint(this.location, true); + var size = this._getSizeinPixels(viewport); + this.adjust(position, size); + + var rotate = 0; + if (viewport.degrees && + this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) { + // BOUNDING_BOX is only valid if both directions get scaled. + // Get replaced by EXACT otherwise. + if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && + this.width !== null && this.height !== null) { + var rect = new $.Rect(position.x, position.y, size.x, size.y); + var boundingBox = this._getBoundingBox(rect, viewport.degrees); + position = boundingBox.getTopLeft(); + size = boundingBox.getSize(); + } else { + rotate = viewport.degrees; + } + } + + return { + position: position, + size: size, + rotate: rotate + }; + }, + + // private + _getSizeinPixels: function(viewport) { var width = this.size.x; var height = this.size.y; if (this.width !== null || this.height !== null) { @@ -309,36 +337,30 @@ height = eltSize.y; } } - var size = new $.Point(width, height); - this.adjust(position, size); + return new $.Point(width, height); + }, - var rotate = 0; - // 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 boundingBox = new $.Rect( - position.x, position.y, size.x, size.y, viewport.degrees) - .getBoundingBox(); - position = boundingBox.getTopLeft(); - size = boundingBox.getSize(); - } else if (this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) { - rotate = viewport.degrees; + // private + _getBoundingBox: function(rect, degrees) { + var refPoint = new $.Point(rect.x, rect.y); + var properties = $.Placement.properties[this.placement]; + if (properties) { + if (properties.isHorizontallyCentered) { + refPoint.x += rect.width / 2; + } else if (properties.isRight) { + refPoint.x += rect.width; + } + if (properties.isVerticallyCentered) { + refPoint.y += rect.height / 2; + } else if (properties.isBottom) { + refPoint.y += rect.height; + } } - - return { - position: position, - size: size, - rotate: rotate - }; + return rect.rotate(degrees, refPoint).getBoundingBox(); }, // private _getTransformOrigin: function() { - if (this.scales) { - return "top left"; - } - var result = ""; var properties = $.Placement.properties[this.placement]; if (!properties) { From c8ed3893ad854d35c01beb8d384612d06fc2204c Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 31 Mar 2016 16:59:26 -0400 Subject: [PATCH 10/19] Fix method name. --- src/overlay.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index 5be33b8f..fa5ddefa 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -287,7 +287,7 @@ // private _getOverlayPositionAndSize: function(viewport) { var position = viewport.pixelFromPoint(this.location, true); - var size = this._getSizeinPixels(viewport); + var size = this._getSizeInPixels(viewport); this.adjust(position, size); var rotate = 0; @@ -314,7 +314,7 @@ }, // private - _getSizeinPixels: function(viewport) { + _getSizeInPixels: function(viewport) { var width = this.size.x; var height = this.size.y; if (this.width !== null || this.height !== null) { From 0685d8a3a4a4ef1b4e4756c13bb527a227c9cba4 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 1 Apr 2016 09:19:40 -0400 Subject: [PATCH 11/19] Use outline instead of border in overlay demo. --- test/demo/overlay.html | 59 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/test/demo/overlay.html b/test/demo/overlay.html index 57435014..527ebef3 100644 --- a/test/demo/overlay.html +++ b/test/demo/overlay.html @@ -39,41 +39,41 @@ viewer.addOverlay({ element: elt, location: new OpenSeadragon.Rect(0.21, 0.21, 0.099, 0.099), - rotationMode: OpenSeadragon.OverlayRotationMode.EXACT - }); - - elt = document.createElement("div"); - elt.className = "runtime-overlay"; - elt.style.background = "white"; - elt.style.border = "3px solid red"; - elt.style.width = "100px"; - elt.textContent = "Scaled vertically"; - viewer.addOverlay({ - element: elt, - location: new OpenSeadragon.Point(0.6, 0.6), - height: 0.1, - placement: OpenSeadragon.Placement.TOP_LEFT - }); - - elt = document.createElement("div"); - elt.className = "runtime-overlay"; - elt.style.background = "white"; - elt.style.opacity = "0.5"; - elt.style.border = "1px solid blue"; - elt.style.height = "100px"; - elt.textContent = "Scaled horizontally"; - viewer.addOverlay({ - element: elt, - location: new OpenSeadragon.Point(0.1, 0.5), - width: 0.1, rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX }); + elt = document.createElement("div"); + elt.className = "runtime-overlay"; + elt.style.background = "white"; + elt.style.outline = "3px solid red"; + elt.style.width = "100px"; + elt.textContent = "Scaled vertically"; + viewer.addOverlay({ + element: elt, + location: new OpenSeadragon.Point(0.6, 0.6), + height: 0.1, + placement: OpenSeadragon.Placement.TOP_LEFT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }); + elt = document.createElement("div"); elt.className = "runtime-overlay"; elt.style.background = "white"; elt.style.opacity = "0.5"; - elt.style.border = "5px solid pink"; + elt.style.outline = "1px solid blue"; + elt.style.height = "100px"; + elt.textContent = "Scaled horizontally"; + viewer.addOverlay({ + element: elt, + location: new OpenSeadragon.Point(0.1, 0.5), + width: 0.1 + }); + + elt = document.createElement("div"); + elt.className = "runtime-overlay"; + elt.style.background = "white"; + elt.style.opacity = "0.5"; + elt.style.outline = "5px solid pink"; elt.style.width = "100px"; elt.style.height = "100px"; elt.textContent = "Not scaled, centered in the middle"; @@ -81,7 +81,8 @@ element: elt, location: new OpenSeadragon.Point(0.5, 0.5), placement: OpenSeadragon.Placement.CENTER, - checkResize: false + checkResize: false, + rotationMode: OpenSeadragon.OverlayRotationMode.EXACT }); }); From bd62d56a37c4d9f73e4266af6b2e59a20329a27d Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 1 Apr 2016 13:29:09 -0400 Subject: [PATCH 12/19] Fix Overlays.getBounds with rotation. --- src/overlay.js | 49 ++++---- test/modules/overlays.js | 239 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 268 insertions(+), 20 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index fa5ddefa..97b5b80a 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -131,7 +131,6 @@ /** @lends OpenSeadragon.Overlay.prototype */ $.Overlay.prototype = { - // private _init: function(options) { this.location = options.location; @@ -157,7 +156,6 @@ this.placement = $.Placement.TOP_LEFT; } }, - /** * Internal function to adjust the position of an overlay * depending on it size and placement. @@ -181,7 +179,6 @@ position.y -= size.y; } }, - /** * @function */ @@ -225,7 +222,6 @@ style[transformProp] = ""; } }, - /** * @function * @param {Element} container @@ -283,7 +279,6 @@ } } }, - // private _getOverlayPositionAndSize: function(viewport) { var position = viewport.pixelFromPoint(this.location, true); @@ -312,7 +307,6 @@ rotate: rotate }; }, - // private _getSizeInPixels: function(viewport) { var width = this.size.x; @@ -339,26 +333,29 @@ } return new $.Point(width, height); }, - // private _getBoundingBox: function(rect, degrees) { - var refPoint = new $.Point(rect.x, rect.y); + var refPoint = this._getPlacementPoint(rect); + return rect.rotate(degrees, refPoint).getBoundingBox(); + }, + // private + _getPlacementPoint: function(rect) { + var result = new $.Point(rect.x, rect.y); var properties = $.Placement.properties[this.placement]; if (properties) { if (properties.isHorizontallyCentered) { - refPoint.x += rect.width / 2; + result.x += rect.width / 2; } else if (properties.isRight) { - refPoint.x += rect.width; + result.x += rect.width; } if (properties.isVerticallyCentered) { - refPoint.y += rect.height / 2; + result.y += rect.height / 2; } else if (properties.isBottom) { - refPoint.y += rect.height; + result.y += rect.height; } } - return rect.rotate(degrees, refPoint).getBoundingBox(); + return result; }, - // private _getTransformOrigin: function() { var result = ""; @@ -378,7 +375,6 @@ } return result; }, - /** * Changes the overlay settings. * @function @@ -403,7 +399,6 @@ rotationMode: options.rotationMode || this.rotationMode }); }, - /** * Returns the current bounds of the overlay in viewport coordinates * @function @@ -411,11 +406,11 @@ * @returns {OpenSeadragon.Rect} overlay bounds */ getBounds: function(viewport) { + $.console.assert(!viewport, 'Calling Overlay.getBounds withouth ' + + 'specifying a viewport is deprecated.'); var width = this.width; var height = this.height; if (width === null || height === null) { - $.console.assert(!viewport, 'The viewport must be specified to' + - ' get the bounds of a not entirely scaling overlay'); var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true); if (width === null) { width = size.x; @@ -426,7 +421,23 @@ } var location = this.location.clone(); this.adjust(location, new $.Point(width, height)); - return new $.Rect(location.x, location.y, width, height); + return this._adjustBoundsForRotation( + viewport, new $.Rect(location.x, location.y, width, height)); + }, + + _adjustBoundsForRotation: function(viewport, bounds) { + if (!viewport || + viewport.degrees === 0 || + this.rotationMode === $.OverlayRotationMode.EXACT) { + return bounds; + } + // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT + if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && + (this.width === null || this.height === null)) { + return bounds; + } + return bounds.rotate(-viewport.degrees, + this._getPlacementPoint(bounds)); } }; diff --git a/test/modules/overlays.js b/test/modules/overlays.js index 496c4f89..0183d61f 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -658,6 +658,7 @@ }); }); + // ---------- asyncTest('Overlay.getBounds', function() { viewer = OpenSeadragon({ id: 'example-overlays', @@ -710,7 +711,7 @@ viewer._drawOverlays(); var actualBounds = viewer.getOverlayById("fully-scaled-overlay") - .getBounds(); + .getBounds(viewer.viewport); var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1); ok(expectedBounds.equals(actualBounds), "The fully scaled overlay should have bounds " + @@ -745,4 +746,240 @@ }); }); + // ---------- + asyncTest('Fully scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var viewport = viewer.viewport; + + var $overlay = $("#fully-scaled-overlay"); + var expectedSize = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)); + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(1, 1)) + .minus(expectedSize); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedSize.x, epsilon, + "Scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedSize.y, epsilon, + "Scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1) + .rotate(-45, new OpenSeadragon.Point(1, 1)); + ok(expectedBounds.equals(actualBounds), + "The fully scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Horizontally scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "horizontally-scaled-overlay", + x: 0.5, + y: 0.5, + width: 1, + placement: OpenSeadragon.Placement.CENTER, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var $overlay = $("#horizontally-scaled-overlay"); + var notScaledWidth = 100; + var notScaledHeight = 100; + $overlay.get(0).style.height = notScaledHeight + "px"; + + var viewport = viewer.viewport; + var notScaledSize = viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var expectedWidth = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)).x; + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(0.5, 0.5)) + .minus(new OpenSeadragon.Point(expectedWidth / 2, notScaledHeight / 2)); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Horizontally scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Horizontally scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedWidth, epsilon, + "Horizontally scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, notScaledHeight, epsilon, + "Horizontally scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("horizontally-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y) + .rotate(-45, new OpenSeadragon.Point(0.5, 0.5)); + ok(expectedBounds.equals(actualBounds), + "The horizontally scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Vertically scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "vertically-scaled-overlay", + x: 0, + y: 0.5, + height: 1, + placement: OpenSeadragon.Placement.LEFT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var $overlay = $("#vertically-scaled-overlay"); + var notScaledWidth = 100; + var notScaledHeight = 100; + $overlay.get(0).style.width = notScaledWidth + "px"; + + var viewport = viewer.viewport; + var notScaledSize = viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var expectedHeight = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)).y; + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(0, 0.5)) + .minus(new OpenSeadragon.Point(0, expectedHeight / 2)); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Vertically scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Vertically scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, notScaledWidth, epsilon, + "Vertically scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedHeight, epsilon, + "Vertically scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("vertically-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 0, 0, notScaledSize.x, 1) + .rotate(-45, new OpenSeadragon.Point(0, 0.5)); + ok(expectedBounds.equals(actualBounds), + "The vertically scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Not scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "not-scaled-overlay", + x: 1, + y: 0, + placement: OpenSeadragon.Placement.TOP_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var $overlay = $("#not-scaled-overlay"); + var notScaledWidth = 100; + var notScaledHeight = 100; + $overlay.get(0).style.width = notScaledWidth + "px"; + $overlay.get(0).style.height = notScaledHeight + "px"; + + var viewport = viewer.viewport; + var notScaledSize = viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(1, 0)) + .minus(new OpenSeadragon.Point(notScaledWidth, 0)); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Not scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Not scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, notScaledWidth, epsilon, + "Not scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, notScaledHeight, epsilon, + "Not scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("not-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y) + .rotate(-45, new OpenSeadragon.Point(1, 0)); + ok(expectedBounds.equals(actualBounds), + "Not scaled overlay should have bounds " + + expectedBounds.toString() + " but found " + actualBounds); + + start(); + }); + }); })(); From fafb7f8db649369340db6a18c266905ae22ddb02 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 1 Apr 2016 13:31:36 -0400 Subject: [PATCH 13/19] Readd blank lines. --- src/overlay.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/overlay.js b/src/overlay.js index 97b5b80a..d104e8cd 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -131,6 +131,7 @@ /** @lends OpenSeadragon.Overlay.prototype */ $.Overlay.prototype = { + // private _init: function(options) { this.location = options.location; @@ -156,6 +157,7 @@ this.placement = $.Placement.TOP_LEFT; } }, + /** * Internal function to adjust the position of an overlay * depending on it size and placement. @@ -179,6 +181,7 @@ position.y -= size.y; } }, + /** * @function */ @@ -222,6 +225,7 @@ style[transformProp] = ""; } }, + /** * @function * @param {Element} container @@ -279,6 +283,7 @@ } } }, + // private _getOverlayPositionAndSize: function(viewport) { var position = viewport.pixelFromPoint(this.location, true); @@ -307,6 +312,7 @@ rotate: rotate }; }, + // private _getSizeInPixels: function(viewport) { var width = this.size.x; @@ -333,11 +339,13 @@ } return new $.Point(width, height); }, + // private _getBoundingBox: function(rect, degrees) { var refPoint = this._getPlacementPoint(rect); return rect.rotate(degrees, refPoint).getBoundingBox(); }, + // private _getPlacementPoint: function(rect) { var result = new $.Point(rect.x, rect.y); @@ -356,6 +364,7 @@ } return result; }, + // private _getTransformOrigin: function() { var result = ""; @@ -375,6 +384,7 @@ } return result; }, + /** * Changes the overlay settings. * @function @@ -399,6 +409,7 @@ rotationMode: options.rotationMode || this.rotationMode }); }, + /** * Returns the current bounds of the overlay in viewport coordinates * @function From 5f9053fb6eecf35025b395fd45146f8b618e447b Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 1 Apr 2016 15:46:43 -0400 Subject: [PATCH 14/19] Fix Overlay.getBounds with BOUNDING_BOX rotation mode. --- src/overlay.js | 21 ++++++++++---- src/viewport.js | 14 ++++++++++ test/modules/overlays.js | 59 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index d104e8cd..7465d573 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -417,7 +417,7 @@ * @returns {OpenSeadragon.Rect} overlay bounds */ getBounds: function(viewport) { - $.console.assert(!viewport, 'Calling Overlay.getBounds withouth ' + + $.console.assert(viewport, 'Calling Overlay.getBounds withouth ' + 'specifying a viewport is deprecated.'); var width = this.width; var height = this.height; @@ -442,11 +442,22 @@ this.rotationMode === $.OverlayRotationMode.EXACT) { return bounds; } - // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT - if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && - (this.width === null || this.height === null)) { - return bounds; + if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) { + // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT + if (this.width === null || this.height === null) { + return bounds; + } + // It is easier to just compute the position and size and + // convert to viewport coordinates. + var positionAndSize = this._getOverlayPositionAndSize(viewport); + return viewport.viewerElementToViewportRectangle(new $.Rect( + positionAndSize.position.x, + positionAndSize.position.y, + positionAndSize.size.x, + positionAndSize.size.y)); } + + // NO_ROTATION case return bounds.rotate(-viewport.degrees, this._getPlacementPoint(bounds)); } diff --git a/src/viewport.js b/src/viewport.js index 821d8075..13841da7 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -1262,6 +1262,20 @@ $.Viewport.prototype = { return this.pixelFromPoint( point, true ); }, + /** + * Convert a rectangle in pixel coordinates relative to the viewer element + * to viewport coordinates. + * @param {OpenSeadragon.Rect} rectangle the rectangle to convert + * @returns {OpenSeadragon.Rect} the converted rectangle + */ + viewerElementToViewportRectangle: function(rectangle) { + return $.Rect.fromSummits( + this.pointFromPixel(rectangle.getTopLeft(), true), + this.pointFromPixel(rectangle.getTopRight(), true), + this.pointFromPixel(rectangle.getBottomLeft(), true) + ); + }, + /** * Convert a rectangle in viewport coordinates to pixel coordinates relative * to the viewer element. diff --git a/test/modules/overlays.js b/test/modules/overlays.js index 0183d61f..49eb0c4f 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -982,4 +982,63 @@ start(); }); }); + + // ---------- + asyncTest('Fully scaled overlay rotation mode BOUNDING_BOX', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX + }] + }); + + viewer.addOnceHandler('open', function() { + var viewport = viewer.viewport; + + var $overlay = $("#fully-scaled-overlay"); + var expectedRect = viewport.viewportToViewerElementRectangle( + new OpenSeadragon.Rect(0, 0, 1, 1)).getBoundingBox(); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedRect.x, epsilon, + "Scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedRect.y, epsilon, + "Scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedRect.width, epsilon, + "Scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedRect.height, epsilon, + "Scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 0.5, -0.5, Math.sqrt(2), Math.sqrt(2), 45); + var boundsEpsilon = 0.000001; + Util.assessNumericValue(actualBounds.x, expectedBounds.x, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.x"); + Util.assessNumericValue(actualBounds.y, expectedBounds.y, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.y"); + Util.assessNumericValue(actualBounds.width, expectedBounds.width, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.width"); + Util.assessNumericValue(actualBounds.height, expectedBounds.height, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.height"); + Util.assessNumericValue(actualBounds.degrees, expectedBounds.degrees, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.degrees"); + + start(); + }); + }); + })(); From 824dc192bcb92edf15eb381e2586a8eee8fe63d0 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 1 Apr 2016 16:54:29 -0400 Subject: [PATCH 15/19] Add unit tests for overlays with rotation mode EXACT --- test/modules/overlays.js | 81 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/test/modules/overlays.js b/test/modules/overlays.js index 49eb0c4f..76e91272 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -715,7 +715,7 @@ var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1); ok(expectedBounds.equals(actualBounds), "The fully scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); actualBounds = viewer.getOverlayById("horizontally-scaled-overlay") @@ -724,7 +724,7 @@ 0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y); ok(expectedBounds.equals(actualBounds), "The horizontally scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); actualBounds = viewer.getOverlayById("vertically-scaled-overlay") .getBounds(viewer.viewport); @@ -732,7 +732,7 @@ 0, 0, notScaledSize.x, 1); ok(expectedBounds.equals(actualBounds), "The vertically scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); actualBounds = viewer.getOverlayById("not-scaled-overlay") .getBounds(viewer.viewport); @@ -740,7 +740,7 @@ 1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y); ok(expectedBounds.equals(actualBounds), "The not scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); start(); }); @@ -793,7 +793,7 @@ .rotate(-45, new OpenSeadragon.Point(1, 1)); ok(expectedBounds.equals(actualBounds), "The fully scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); start(); }); @@ -855,7 +855,7 @@ .rotate(-45, new OpenSeadragon.Point(0.5, 0.5)); ok(expectedBounds.equals(actualBounds), "The horizontally scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); start(); }); @@ -917,7 +917,7 @@ .rotate(-45, new OpenSeadragon.Point(0, 0.5)); ok(expectedBounds.equals(actualBounds), "The vertically scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); start(); }); @@ -977,7 +977,7 @@ .rotate(-45, new OpenSeadragon.Point(1, 0)); ok(expectedBounds.equals(actualBounds), "Not scaled overlay should have bounds " + - expectedBounds.toString() + " but found " + actualBounds); + expectedBounds + " but found " + actualBounds); start(); }); @@ -1041,4 +1041,69 @@ }); }); + // ---------- + asyncTest('Fully scaled overlay rotation mode EXACT', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.EXACT + }] + }); + + viewer.addOnceHandler('open', function() { + var viewport = viewer.viewport; + + var $overlay = $("#fully-scaled-overlay"); + var expectedSize = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)); + var expectedPosition = viewport.pixelFromPoint( + new OpenSeadragon.Point(1, 1)) + .minus(expectedSize); + // We can't rely on jQuery.position with transforms. + var actualStyle = $overlay.get(0).style; + var left = Number(actualStyle.left.replace("px", "")); + var top = Number(actualStyle.top.replace("px", "")); + Util.assessNumericValue(left, expectedPosition.x, epsilon, + "Scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(top, expectedPosition.y, epsilon, + "Scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedSize.x, epsilon, + "Scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedSize.y, epsilon, + "Scaled overlay height should not adjust to rotation."); + + var transformOriginProp = OpenSeadragon.getCssPropertyWithVendorPrefix( + 'transformOrigin'); + var transformProp = OpenSeadragon.getCssPropertyWithVendorPrefix( + 'transform'); + var transformOrigin = actualStyle[transformOriginProp]; + // Some browsers replace "right bottom" by "100% 100%" + ok(transformOrigin.match(/(100% 100%)|(right bottom)/), + "Transform origin should be right bottom. Got: " + transformOrigin); + equal(actualStyle[transformProp], "rotate(45deg)", + "Transform should be rotate(45deg)."); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1); + ok(expectedBounds.equals(actualBounds), + "The fully scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); })(); From 96a032164f285f110cdd480dabf6269633378fd7 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 4 Apr 2016 13:59:51 -0400 Subject: [PATCH 16/19] Update changelog --- changelog.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 962118f8..8fe8397d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,6 +6,9 @@ OPENSEADRAGON CHANGELOG * BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and Viewport.contentAspectY have been removed. (#846) * BREAKING CHANGE: Overlay.scales, Overlay.bounds and Overlay.position have been removed. (#896) + * Overlay.scales can be replaced by Overlay.width !== null && Overlay.height !== null + * The Overlay.getBounds method can be used to get the bounds of the overlay in viewport coordinates + * Overlay.location replaces Overlay.position * DEPRECATION: Viewport.setHomeBounds has been deprecated (#846) * DEPRECATION: the Viewport constructor is now ignoring the contentSize option (#846) * Tile edge smoothing at high zoom (#764) @@ -31,7 +34,7 @@ OPENSEADRAGON CHANGELOG * Fixed issue causing HTML pages to jump unwantedly to the reference strip upon loading (#872) * Added addOnceHandler method to EventSource (#887) * Added TiledImage.fitBounds method (#888) -* Overlays can now be scaled in only one dimension by providing a point location and either width or height (#896) +* Overlays can now be scaled in a single dimension by providing a point location and either width or height (#896) * Added full rotation support to overlays (#729, #193) 2.1.0: From 53d1534cc220a39f046c746c96fd6f38fd71c73f Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 5 Apr 2016 13:05:32 -0400 Subject: [PATCH 17/19] Add old properties for backward compatibility. --- changelog.txt | 8 +++++--- src/overlay.js | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 8fe8397d..8829d474 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,9 +5,11 @@ OPENSEADRAGON CHANGELOG * BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and Viewport.contentAspectY have been removed. (#846) -* BREAKING CHANGE: Overlay.scales, Overlay.bounds and Overlay.position have been removed. (#896) - * Overlay.scales can be replaced by Overlay.width !== null && Overlay.height !== null - * The Overlay.getBounds method can be used to get the bounds of the overlay in viewport coordinates +* BREAKING CHANGE: The Overlay.getBounds method now takes the viewport as parameter. (#896) +* DEPRECATION: Overlay.scales, Overlay.bounds and Overlay.position have been deprecated. (#896) + * Overlay.width !== null should be used to test whether the overlay scales horizontally + * Overlay.height !== null should be used to test whether the overlay scales vertically + * The Overlay.getBounds method should be used to get the bounds of the overlay in viewport coordinates * Overlay.location replaces Overlay.position * DEPRECATION: Viewport.setHomeBounds has been deprecated (#846) * DEPRECATION: the Viewport constructor is now ignoring the contentSize option (#846) diff --git a/src/overlay.js b/src/overlay.js index 7465d573..9c7b4d93 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -156,6 +156,12 @@ this.location = this.location.getTopLeft(); this.placement = $.Placement.TOP_LEFT; } + + // Deprecated properties kept for backward compatibility. + this.scales = this.width !== null && this.height !== null; + this.bounds = new $.Rect( + this.location.x, this.location.y, this.width, this.height); + this.position = this.location; }, /** From 81f439d43070fba72010b0ce2e20595aa8fde5c2 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 6 Apr 2016 09:10:51 -0400 Subject: [PATCH 18/19] Document the viewport parameter as mandatory in Overlay.getBounds. --- src/overlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overlay.js b/src/overlay.js index 9c7b4d93..000b395a 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -419,7 +419,7 @@ /** * Returns the current bounds of the overlay in viewport coordinates * @function - * @param {OpenSeadragon.Viewport} [viewport] the viewport + * @param {OpenSeadragon.Viewport} viewport the viewport * @returns {OpenSeadragon.Rect} overlay bounds */ getBounds: function(viewport) { From cd7bb8a8c4a5e4c52327e6b977aba5d514623933 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 6 Apr 2016 12:55:50 -0400 Subject: [PATCH 19/19] Fix doc and debug message. --- src/overlay.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index 000b395a..76eb347c 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -423,8 +423,8 @@ * @returns {OpenSeadragon.Rect} overlay bounds */ getBounds: function(viewport) { - $.console.assert(viewport, 'Calling Overlay.getBounds withouth ' + - 'specifying a viewport is deprecated.'); + $.console.assert(viewport, + 'A viewport must now be passed to Overlay.getBounds.'); var width = this.width; var height = this.height; if (width === null || height === null) { @@ -442,6 +442,7 @@ viewport, new $.Rect(location.x, location.y, width, height)); }, + // private _adjustBoundsForRotation: function(viewport, bounds) { if (!viewport || viewport.degrees === 0 ||