From e4fca14c33331b8dcde54501f4d253345aa3a18e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 21 Mar 2016 16:11:50 -0400 Subject: [PATCH 1/4] Add TiledImage.fitInBounds method. --- Gruntfile.js | 1 + src/placement.js | 125 +++++++++++++++++++++++++++++++++++++ src/tiledimage.js | 42 +++++++++++++ test/coverage.html | 1 + test/modules/tiledimage.js | 56 +++++++++++++++-- 5 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 src/placement.js diff --git a/Gruntfile.js b/Gruntfile.js index d5891153..9871806b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,6 +28,7 @@ module.exports = function(grunt) { "src/mousetracker.js", "src/control.js", "src/controldock.js", + "src/placement.js", "src/viewer.js", "src/navigator.js", "src/strings.js", diff --git a/src/placement.js b/src/placement.js new file mode 100644 index 00000000..479c1d0c --- /dev/null +++ b/src/placement.js @@ -0,0 +1,125 @@ +/* + * OpenSeadragon - Placement + * + * Copyright (C) 2010-2016 OpenSeadragon contributors + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of CodePlex Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function($) { + + /** + * An enumeration of positions to anchor an element. + * @memberOf OpenSeadragon + * @static + * @property {OpenSeadragon.Placement} CENTER + * @property {OpenSeadragon.Placement} TOP_LEFT + * @property {OpenSeadragon.Placement} TOP + * @property {OpenSeadragon.Placement} TOP_RIGHT + * @property {OpenSeadragon.Placement} RIGHT + * @property {OpenSeadragon.Placement} BOTTOM_RIGHT + * @property {OpenSeadragon.Placement} BOTTOM + * @property {OpenSeadragon.Placement} BOTTOM_LEFT + * @property {OpenSeadragon.Placement} LEFT + */ + $.Placement = { + CENTER: { + isLeft: false, + isHorizontallyCentered: true, + isRight: false, + isTop: false, + isVerticallyCentered: true, + isBottom: false + }, + TOP_LEFT: { + isLeft: true, + isHorizontallyCentered: false, + isRight: false, + isTop: true, + isVerticallyCentered: false, + isBottom: false + }, + TOP: { + isLeft: false, + isHorizontallyCentered: true, + isRight: false, + isTop: true, + isVerticallyCentered: false, + isBottom: false + }, + TOP_RIGHT: { + isLeft: false, + isHorizontallyCentered: false, + isRight: true, + isTop: true, + isVerticallyCentered: false, + isBottom: false + }, + RIGHT: { + isLeft: false, + isHorizontallyCentered: false, + isRight: true, + isTop: false, + isVerticallyCentered: true, + isBottom: false + }, + BOTTOM_RIGHT: { + isLeft: false, + isHorizontallyCentered: false, + isRight: true, + isTop: false, + isVerticallyCentered: false, + isBottom: true + }, + BOTTOM: { + isLeft: false, + isHorizontallyCentered: true, + isRight: false, + isTop: false, + isVerticallyCentered: false, + isBottom: true + }, + BOTTOM_LEFT: { + isLeft: true, + isHorizontallyCentered: false, + isRight: false, + isTop: false, + isVerticallyCentered: false, + isBottom: true + }, + LEFT: { + isLeft: true, + isHorizontallyCentered: false, + isRight: false, + isTop: false, + isVerticallyCentered: true, + isBottom: false + } + }; + +}(OpenSeadragon)); diff --git a/src/tiledimage.js b/src/tiledimage.js index 9f3acf46..dbd0af43 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -543,6 +543,48 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag this._setScale(height / this.normHeight, immediately); }, + /** + * Positions and scales the TiledImage to fit in the specified bounds. + * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change + * twice + * @param {OpenSeadragon.Rect} bounds The bounds to fit the image into. + * @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER] + * How to anchor the image in the bounds. + * @param {Boolean} [immediately=false] Whether to animate to the new size + * or snap immediately. + * @fires OpenSeadragon.TiledImage.event:bounds-change + */ + fitInBounds: function(bounds, anchor, immediately) { + anchor = anchor || $.Placement.CENTER; + if (bounds.getAspectRatio() > this.contentAspectX) { + // We will have margins on the X axis + var targetWidth = bounds.height * this.contentAspectX; + var marginLeft = 0; + if (anchor.isHorizontallyCentered) { + marginLeft = (bounds.width - targetWidth) / 2; + } else if (anchor.isRight) { + marginLeft = bounds.width - targetWidth; + } + this.setPosition( + new $.Point(bounds.x + marginLeft, bounds.y), + immediately); + this.setHeight(bounds.height, immediately); + } else { + // We will have margins on the Y axis + var targetHeight = bounds.width / this.contentAspectX; + var marginTop = 0; + if (anchor.isVerticallyCentered) { + marginTop = (bounds.height - targetHeight) / 2; + } else if (anchor.isBottom) { + marginTop = bounds.height - targetHeight; + } + this.setPosition( + new $.Point(bounds.x, bounds.y + marginTop), + immediately); + this.setWidth(bounds.width, immediately); + } + }, + /** * @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle, * in image pixels, or null if none. diff --git a/test/coverage.html b/test/coverage.html index bcf893ee..81ffe579 100644 --- a/test/coverage.html +++ b/test/coverage.html @@ -22,6 +22,7 @@ + diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 07f23f55..d43ec41b 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -4,18 +4,18 @@ var viewer; module('TiledImage', { - setup: function () { + setup: function() { var example = $('
').appendTo("#qunit-fixture"); testLog.reset(); viewer = OpenSeadragon({ - id: 'example', - prefixUrl: '/build/openseadragon/images/', + id: 'example', + prefixUrl: '/build/openseadragon/images/', springStiffness: 100 // Faster animation = faster tests }); }, - teardown: function () { + teardown: function() { if (viewer && viewer.close) { viewer.close(); } @@ -87,7 +87,7 @@ // ---------- asyncTest('animation', function() { - viewer.addHandler("open", function () { + viewer.addHandler("open", function() { var image = viewer.world.getItemAt(0); propEqual(image.getBounds(), new OpenSeadragon.Rect(0, 0, 1, 1), 'target bounds on open'); propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds on open'); @@ -257,4 +257,50 @@ }); }); + asyncTest('fitInBounds', function() { + + function assertRectEquals(actual, expected, message) { + ok(actual.equals(expected), message + ' should be ' + + expected.toString() + ', found ' + actual.toString()); + } + + viewer.addHandler('open', function openHandler() { + viewer.removeHandler('open', openHandler); + + var squareImage = viewer.world.getItemAt(0); + squareImage.fitInBounds( + new OpenSeadragon.Rect(0, 0, 1, 2), + OpenSeadragon.Placement.CENTER, + true); + var actualBounds = squareImage.getBounds(true); + var expectedBounds = new OpenSeadragon.Rect(0, 0.5, 1, 1); + assertRectEquals(actualBounds, expectedBounds, 'Square image bounds'); + + var tallImage = viewer.world.getItemAt(1); + tallImage.fitInBounds( + new OpenSeadragon.Rect(0, 0, 1, 2), + OpenSeadragon.Placement.TOP_LEFT, + true); + actualBounds = tallImage.getBounds(true); + expectedBounds = new OpenSeadragon.Rect(0, 0, 0.5, 2); + assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds'); + + var wideImage = viewer.world.getItemAt(2); + wideImage.fitInBounds( + new OpenSeadragon.Rect(0, 0, 1, 2), + OpenSeadragon.Placement.BOTTOM_RIGHT, + true); + actualBounds = wideImage.getBounds(true); + expectedBounds = new OpenSeadragon.Rect(0, 1.75, 1, 0.25); + assertRectEquals(actualBounds, expectedBounds, 'Wide image bounds'); + start(); + }); + + viewer.open([ + '/test/data/testpattern.dzi', + '/test/data/tall.dzi', + '/test/data/wide.dzi' + ]); + }); + })(); From 9c461824b3b038e855efd4567fa3e6a7d1f29498 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 22 Mar 2016 15:50:48 -0400 Subject: [PATCH 2/4] Update OpenSeadragon.Placement to be serializable. --- src/openseadragon.js | 17 ++++- src/overlay.js | 73 ++++++------------ src/placement.js | 157 +++++++++++++++++++++------------------ src/tiledimage.js | 9 ++- src/viewer.js | 12 +-- test/modules/overlays.js | 6 +- 6 files changed, 137 insertions(+), 137 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index a129f083..07523516 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -154,7 +154,7 @@ * created. * * placement a string to define the relative position to the viewport. * Only used if no width and height are specified. Default: 'TOP_LEFT'. - * See {@link OpenSeadragon.OverlayPlacement} for possible values. + * See {@link OpenSeadragon.Placement} for possible values. * * @property {String} [xmlPath=null] * DEPRECATED. A relative path to load a DZI file from the server. @@ -842,6 +842,21 @@ if (typeof define === 'function' && define.amd) { return true; }; + /** + * Shim around Object.freeze. Does nothing if Object.freeze is not supported. + * @param {Object} obj The object to freeze. + * @return {Object} obj The frozen object. + */ + $.freezeObject = function(obj) { + if (Object.freeze) { + $.freezeObject = Object.freeze; + } else { + $.freezeObject = function(obj) { + return obj; + }; + } + return $.freezeObject(obj); + }; /** * True if the browser supports the HTML5 canvas element diff --git a/src/overlay.js b/src/overlay.js index ffdd21bd..7e121950 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -37,6 +37,8 @@ /** * An enumeration of positions that an overlay may be assigned relative to * the viewport. + * It is identical to OpenSeadragon.Placement but is kept for backward + * compatibility. * @member OverlayPlacement * @memberof OpenSeadragon * @static @@ -51,17 +53,7 @@ * @property {Number} BOTTOM_LEFT * @property {Number} LEFT */ - $.OverlayPlacement = { - CENTER: 0, - TOP_LEFT: 1, - TOP: 2, - TOP_RIGHT: 3, - RIGHT: 4, - BOTTOM_RIGHT: 5, - BOTTOM: 6, - BOTTOM_LEFT: 7, - LEFT: 8 - }; + $.OverlayPlacement = $.Placement; /** * @class Overlay @@ -75,7 +67,7 @@ * 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. - * @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.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}. * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw] @@ -126,8 +118,7 @@ this.style = options.element.style; // rects are always top-left this.placement = options.location instanceof $.Point ? - options.placement : - $.OverlayPlacement.TOP_LEFT; + options.placement : $.Placement.TOP_LEFT; this.onDraw = options.onDraw; this.checkResize = options.checkResize === undefined ? true : options.checkResize; @@ -138,42 +129,23 @@ /** * @function - * @param {OpenSeadragon.OverlayPlacement} position + * @param {OpenSeadragon.Point} position * @param {OpenSeadragon.Point} size */ - adjust: function( position, size ) { - switch ( this.placement ) { - case $.OverlayPlacement.TOP_LEFT: - break; - case $.OverlayPlacement.TOP: - position.x -= size.x / 2; - break; - case $.OverlayPlacement.TOP_RIGHT: - position.x -= size.x; - break; - case $.OverlayPlacement.RIGHT: - position.x -= size.x; - position.y -= size.y / 2; - break; - case $.OverlayPlacement.BOTTOM_RIGHT: - position.x -= size.x; - position.y -= size.y; - break; - case $.OverlayPlacement.BOTTOM: - position.x -= size.x / 2; - position.y -= size.y; - break; - case $.OverlayPlacement.BOTTOM_LEFT: - position.y -= size.y; - break; - case $.OverlayPlacement.LEFT: - position.y -= size.y / 2; - break; - default: - case $.OverlayPlacement.CENTER: - position.x -= size.x / 2; - position.y -= size.y / 2; - break; + adjust: function(position, size) { + var properties = $.Placement.properties[this.placement]; + if (!properties) { + return; + } + if (properties.isHorizontallyCentered) { + position.x -= size.x / 2; + } else if (properties.isRight) { + position.x -= size.x; + } + if (properties.isVerticallyCentered) { + position.y -= size.y / 2; + } else if (properties.isBottom) { + position.y -= size.y; } }, @@ -298,7 +270,7 @@ /** * @function * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - * @param {OpenSeadragon.OverlayPlacement} position + * @param {OpenSeadragon.Placement} position */ update: function( location, placement ) { this.scales = location instanceof $.Rect; @@ -310,8 +282,7 @@ ); // rects are always top-left this.placement = location instanceof $.Point ? - placement : - $.OverlayPlacement.TOP_LEFT; + placement : $.Placement.TOP_LEFT; }, /** diff --git a/src/placement.js b/src/placement.js index 479c1d0c..a90bf5da 100644 --- a/src/placement.js +++ b/src/placement.js @@ -47,79 +47,90 @@ * @property {OpenSeadragon.Placement} BOTTOM_LEFT * @property {OpenSeadragon.Placement} LEFT */ - $.Placement = { - CENTER: { - isLeft: false, - isHorizontallyCentered: true, - isRight: false, - isTop: false, - isVerticallyCentered: true, - isBottom: false - }, - TOP_LEFT: { - isLeft: true, - isHorizontallyCentered: false, - isRight: false, - isTop: true, - isVerticallyCentered: false, - isBottom: false - }, - TOP: { - isLeft: false, - isHorizontallyCentered: true, - isRight: false, - isTop: true, - isVerticallyCentered: false, - isBottom: false - }, - TOP_RIGHT: { - isLeft: false, - isHorizontallyCentered: false, - isRight: true, - isTop: true, - isVerticallyCentered: false, - isBottom: false - }, - RIGHT: { - isLeft: false, - isHorizontallyCentered: false, - isRight: true, - isTop: false, - isVerticallyCentered: true, - isBottom: false - }, - BOTTOM_RIGHT: { - isLeft: false, - isHorizontallyCentered: false, - isRight: true, - isTop: false, - isVerticallyCentered: false, - isBottom: true - }, - BOTTOM: { - isLeft: false, - isHorizontallyCentered: true, - isRight: false, - isTop: false, - isVerticallyCentered: false, - isBottom: true - }, - BOTTOM_LEFT: { - isLeft: true, - isHorizontallyCentered: false, - isRight: false, - isTop: false, - isVerticallyCentered: false, - isBottom: true - }, - LEFT: { - isLeft: true, - isHorizontallyCentered: false, - isRight: false, - isTop: false, - isVerticallyCentered: true, - isBottom: false + $.Placement = $.freezeObject({ + CENTER: 0, + TOP_LEFT: 1, + TOP: 2, + TOP_RIGHT: 3, + RIGHT: 4, + BOTTOM_RIGHT: 5, + BOTTOM: 6, + BOTTOM_LEFT: 7, + LEFT: 8, + properties: { + 0: { + isLeft: false, + isHorizontallyCentered: true, + isRight: false, + isTop: false, + isVerticallyCentered: true, + isBottom: false + }, + 1: { + isLeft: true, + isHorizontallyCentered: false, + isRight: false, + isTop: true, + isVerticallyCentered: false, + isBottom: false + }, + 2: { + isLeft: false, + isHorizontallyCentered: true, + isRight: false, + isTop: true, + isVerticallyCentered: false, + isBottom: false + }, + 3: { + isLeft: false, + isHorizontallyCentered: false, + isRight: true, + isTop: true, + isVerticallyCentered: false, + isBottom: false + }, + 4: { + isLeft: false, + isHorizontallyCentered: false, + isRight: true, + isTop: false, + isVerticallyCentered: true, + isBottom: false + }, + 5: { + isLeft: false, + isHorizontallyCentered: false, + isRight: true, + isTop: false, + isVerticallyCentered: false, + isBottom: true + }, + 6: { + isLeft: false, + isHorizontallyCentered: true, + isRight: false, + isTop: false, + isVerticallyCentered: false, + isBottom: true + }, + 7: { + isLeft: true, + isHorizontallyCentered: false, + isRight: false, + isTop: false, + isVerticallyCentered: false, + isBottom: true + }, + 8: { + isLeft: true, + isHorizontallyCentered: false, + isRight: false, + isTop: false, + isVerticallyCentered: true, + isBottom: false + } } - }; + }); }(OpenSeadragon)); diff --git a/src/tiledimage.js b/src/tiledimage.js index dbd0af43..bec4a6ef 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -556,13 +556,14 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ fitInBounds: function(bounds, anchor, immediately) { anchor = anchor || $.Placement.CENTER; + var anchorProperties = $.Placement.properties[anchor]; if (bounds.getAspectRatio() > this.contentAspectX) { // We will have margins on the X axis var targetWidth = bounds.height * this.contentAspectX; var marginLeft = 0; - if (anchor.isHorizontallyCentered) { + if (anchorProperties.isHorizontallyCentered) { marginLeft = (bounds.width - targetWidth) / 2; - } else if (anchor.isRight) { + } else if (anchorProperties.isRight) { marginLeft = bounds.width - targetWidth; } this.setPosition( @@ -573,9 +574,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // We will have margins on the Y axis var targetHeight = bounds.width / this.contentAspectX; var marginTop = 0; - if (anchor.isVerticallyCentered) { + if (anchorProperties.isVerticallyCentered) { marginTop = (bounds.height - targetHeight) / 2; - } else if (anchor.isBottom) { + } else if (anchorProperties.isBottom) { marginTop = bounds.height - targetHeight; } this.setPosition( diff --git a/src/viewer.js b/src/viewer.js index ab782484..3e5e4b04 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1785,7 +1785,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * the element which will be overlayed. Or an Object specifying the configuration for the overlay * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or * rectangle which will be overlayed. This is a viewport relative location. - * @param {OpenSeadragon.OverlayPlacement} placement - The position of the + * @param {OpenSeadragon.Placement} placement - The position of the * viewport which the location coordinates will be treated as relative * to. * @param {function} onDraw - If supplied the callback is called when the overlay @@ -1827,7 +1827,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. * @property {Element} element - The overlay element. * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location - * @property {OpenSeadragon.OverlayPlacement} placement + * @property {OpenSeadragon.Placement} placement * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'add-overlay', { @@ -1846,7 +1846,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * the element which is overlayed. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or * rectangle which will be overlayed. This is a viewport relative location. - * @param {OpenSeadragon.OverlayPlacement} placement - The position of the + * @param {OpenSeadragon.Placement} placement - The position of the * viewport which the location coordinates will be treated as relative * to. * @return {OpenSeadragon.Viewer} Chainable. @@ -1872,7 +1872,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * Viewer which raised the event. * @property {Element} element * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location - * @property {OpenSeadragon.OverlayPlacement} placement + * @property {OpenSeadragon.Placement} placement * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent( 'update-overlay', { @@ -2222,8 +2222,8 @@ function getOverlayObject( viewer, overlay ) { } var placement = overlay.placement; - if ( placement && ( $.type( placement ) === "string" ) ) { - placement = $.OverlayPlacement[ overlay.placement.toUpperCase() ]; + if (placement && $.type(placement) === "string") { + placement = $.Placement[overlay.placement.toUpperCase()]; } return new $.Overlay({ diff --git a/test/modules/overlays.js b/test/modules/overlays.js index 3e26b4c4..bb317050 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -397,6 +397,7 @@ 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, @@ -407,10 +408,11 @@ checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), "with CENTER placement." ); + // Check that new OpenSeadragon.Placement is working viewer.updateOverlay( "overlay", scalableOverlayLocation, - OpenSeadragon.OverlayPlacement.BOTTOM_RIGHT ); + OpenSeadragon.Placement.BOTTOM_RIGHT ); viewer.updateOverlay( "fixed-overlay", fixedOverlayLocation, - OpenSeadragon.OverlayPlacement.BOTTOM_RIGHT ); + OpenSeadragon.Placement.BOTTOM_RIGHT ); setTimeout( function() { checkScalableOverlayPosition( "with BOTTOM_RIGHT placement." ); checkFixedOverlayPosition( new OpenSeadragon.Point( -70, -60 ), From 3cacc8edcf96d318e85b88cde91b9dadaf8936b1 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 22 Mar 2016 16:41:28 -0400 Subject: [PATCH 3/4] Add fitBounds option to TiledImage constructor. --- src/tiledimage.js | 15 ++++++++++- src/viewer.js | 6 +++++ test/modules/tiledimage.js | 52 +++++++++++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index bec4a6ef..a925104c 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -52,6 +52,10 @@ * @param {Number} [options.y=0] - Top position, in viewport coordinates. * @param {Number} [options.width=1] - Width, in viewport coordinates. * @param {Number} [options.height] - Height, in viewport coordinates. + * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates + * to fit the image into. If specified, x, y, width and height get ignored. + * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER] + * How to anchor the image in the bounds if options.fitBounds is set. * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to * (portions of the image outside of this area will not be visible). Only works on * browsers that support the HTML5 canvas. @@ -122,6 +126,11 @@ $.TiledImage = function( options ) { delete options.height; } + var fitBounds = options.fitBounds; + delete options.fitBounds; + var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER; + delete options.fitBoundsPlacement; + $.extend( true, this, { //internal state properties @@ -172,6 +181,10 @@ $.TiledImage = function( options ) { this._updateForScale(); + if (fitBounds) { + this.fitBounds(fitBounds, fitBoundsPlacement, true); + } + // We need a callback to give image manipulation a chance to happen this._drawingHandler = function(args) { /** @@ -554,7 +567,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * or snap immediately. * @fires OpenSeadragon.TiledImage.event:bounds-change */ - fitInBounds: function(bounds, anchor, immediately) { + fitBounds: function(bounds, anchor, immediately) { anchor = anchor || $.Placement.CENTER; var anchorProperties = $.Placement.properties[anchor]; if (bounds.getAspectRatio() > this.contentAspectX) { diff --git a/src/viewer.js b/src/viewer.js index 3e5e4b04..f0434037 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1206,6 +1206,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @param {Number} [options.y=0] The Y position for the image in viewport coordinates. * @param {Number} [options.width=1] The width for the image in viewport coordinates. * @param {Number} [options.height] The height for the image in viewport coordinates. + * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates + * to fit the image into. If specified, x, y, width and height get ignored. + * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER] + * How to anchor the image in the bounds if options.fitBounds is set. * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to * (portions of the image outside of this area will not be visible). Only works on * browsers that support the HTML5 canvas. @@ -1341,6 +1345,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, y: queueItem.options.y, width: queueItem.options.width, height: queueItem.options.height, + fitBounds: queueItem.options.fitBounds, + fitBoundsPlacement: queueItem.options.fitBoundsPlacement, clip: queueItem.options.clip, placeholderFillStyle: queueItem.options.placeholderFillStyle, opacity: queueItem.options.opacity, diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index d43ec41b..76b55541 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -257,7 +257,7 @@ }); }); - asyncTest('fitInBounds', function() { + asyncTest('fitBounds', function() { function assertRectEquals(actual, expected, message) { ok(actual.equals(expected), message + ' should be ' + @@ -268,7 +268,7 @@ viewer.removeHandler('open', openHandler); var squareImage = viewer.world.getItemAt(0); - squareImage.fitInBounds( + squareImage.fitBounds( new OpenSeadragon.Rect(0, 0, 1, 2), OpenSeadragon.Placement.CENTER, true); @@ -277,7 +277,7 @@ assertRectEquals(actualBounds, expectedBounds, 'Square image bounds'); var tallImage = viewer.world.getItemAt(1); - tallImage.fitInBounds( + tallImage.fitBounds( new OpenSeadragon.Rect(0, 0, 1, 2), OpenSeadragon.Placement.TOP_LEFT, true); @@ -286,7 +286,7 @@ assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds'); var wideImage = viewer.world.getItemAt(2); - wideImage.fitInBounds( + wideImage.fitBounds( new OpenSeadragon.Rect(0, 0, 1, 2), OpenSeadragon.Placement.BOTTOM_RIGHT, true); @@ -303,4 +303,48 @@ ]); }); + asyncTest('fitBounds in constructor', function() { + + function assertRectEquals(actual, expected, message) { + ok(actual.equals(expected), message + ' should be ' + + expected.toString() + ', found ' + actual.toString()); + } + + viewer.addHandler('open', function openHandler() { + viewer.removeHandler('open', openHandler); + + var squareImage = viewer.world.getItemAt(0); + var actualBounds = squareImage.getBounds(true); + var expectedBounds = new OpenSeadragon.Rect(0, 0.5, 1, 1); + assertRectEquals(actualBounds, expectedBounds, 'Square image bounds'); + + var tallImage = viewer.world.getItemAt(1); + actualBounds = tallImage.getBounds(true); + expectedBounds = new OpenSeadragon.Rect(0, 0, 0.5, 2); + assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds'); + + var wideImage = viewer.world.getItemAt(2); + actualBounds = wideImage.getBounds(true); + expectedBounds = new OpenSeadragon.Rect(0, 1.75, 1, 0.25); + assertRectEquals(actualBounds, expectedBounds, 'Wide image bounds'); + start(); + }); + + viewer.open([{ + tileSource: '/test/data/testpattern.dzi', + x: 1, // should be ignored + y: 1, // should be ignored + width: 2, // should be ignored + fitBounds: new OpenSeadragon.Rect(0, 0, 1, 2) + // No placement specified, should default to CENTER + }, { + tileSource: '/test/data/tall.dzi', + fitBounds: new OpenSeadragon.Rect(0, 0, 1, 2), + fitBoundsPlacement: OpenSeadragon.Placement.TOP_LEFT + }, { + tileSource: '/test/data/wide.dzi', + fitBounds: new OpenSeadragon.Rect(0, 0, 1, 2), + fitBoundsPlacement: OpenSeadragon.Placement.BOTTOM_RIGHT + }]); + }); })(); From a52f4cadc5b41443b2588f3a1207b0310ba4e860 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 24 Mar 2016 11:48:29 -0400 Subject: [PATCH 4/4] Fix TiledImage.fitBounds with clipping. --- src/tiledimage.js | 40 ++++++++++++++++++++++++---------- test/modules/tiledimage.js | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index a925104c..248f0ece 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -570,32 +570,50 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag fitBounds: function(bounds, anchor, immediately) { anchor = anchor || $.Placement.CENTER; var anchorProperties = $.Placement.properties[anchor]; - if (bounds.getAspectRatio() > this.contentAspectX) { + var aspectRatio = this.contentAspectX; + var xOffset = 0; + var yOffset = 0; + var displayedWidthRatio = 1; + var displayedHeightRatio = 1; + if (this._clip) { + aspectRatio = this._clip.getAspectRatio(); + displayedWidthRatio = this._clip.width / this.source.dimensions.x; + displayedHeightRatio = this._clip.height / this.source.dimensions.y; + if (bounds.getAspectRatio() > aspectRatio) { + xOffset = this._clip.x / this._clip.height * bounds.height; + yOffset = this._clip.y / this._clip.height * bounds.height; + } else { + xOffset = this._clip.x / this._clip.width * bounds.width; + yOffset = this._clip.y / this._clip.width * bounds.width; + } + } + + if (bounds.getAspectRatio() > aspectRatio) { // We will have margins on the X axis - var targetWidth = bounds.height * this.contentAspectX; + var height = bounds.height / displayedHeightRatio; var marginLeft = 0; if (anchorProperties.isHorizontallyCentered) { - marginLeft = (bounds.width - targetWidth) / 2; + marginLeft = (bounds.width - bounds.height * aspectRatio) / 2; } else if (anchorProperties.isRight) { - marginLeft = bounds.width - targetWidth; + marginLeft = bounds.width - bounds.height * aspectRatio; } this.setPosition( - new $.Point(bounds.x + marginLeft, bounds.y), + new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset), immediately); - this.setHeight(bounds.height, immediately); + this.setHeight(height, immediately); } else { // We will have margins on the Y axis - var targetHeight = bounds.width / this.contentAspectX; + var width = bounds.width / displayedWidthRatio; var marginTop = 0; if (anchorProperties.isVerticallyCentered) { - marginTop = (bounds.height - targetHeight) / 2; + marginTop = (bounds.height - bounds.width / aspectRatio) / 2; } else if (anchorProperties.isBottom) { - marginTop = bounds.height - targetHeight; + marginTop = bounds.height - bounds.width / aspectRatio; } this.setPosition( - new $.Point(bounds.x, bounds.y + marginTop), + new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop), immediately); - this.setWidth(bounds.width, immediately); + this.setWidth(width, immediately); } }, diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 76b55541..f281d076 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -347,4 +347,48 @@ fitBoundsPlacement: OpenSeadragon.Placement.BOTTOM_RIGHT }]); }); + + asyncTest('fitBounds with clipping', function() { + + function assertRectEquals(actual, expected, message) { + ok(actual.equals(expected), message + ' should be ' + + expected.toString() + ', found ' + actual.toString()); + } + + viewer.addHandler('open', function openHandler() { + viewer.removeHandler('open', openHandler); + + var squareImage = viewer.world.getItemAt(0); + var actualBounds = squareImage.getBounds(true); + var expectedBounds = new OpenSeadragon.Rect(-1, -1, 2, 2); + assertRectEquals(actualBounds, expectedBounds, 'Square image bounds'); + + var tallImage = viewer.world.getItemAt(1); + actualBounds = tallImage.getBounds(true); + expectedBounds = new OpenSeadragon.Rect(1, 1, 2, 8); + assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds'); + + var wideImage = viewer.world.getItemAt(2); + actualBounds = wideImage.getBounds(true); + expectedBounds = new OpenSeadragon.Rect(1, 1, 16, 4); + assertRectEquals(actualBounds, expectedBounds, 'Wide image bounds'); + start(); + }); + + viewer.open([{ + tileSource: '/test/data/testpattern.dzi', + clip: new OpenSeadragon.Rect(500, 500, 500, 500), + fitBounds: new OpenSeadragon.Rect(0, 0, 1, 1) + }, { + tileSource: '/test/data/tall.dzi', + clip: new OpenSeadragon.Rect(0, 0, 250, 100), + fitBounds: new OpenSeadragon.Rect(1, 1, 1, 2), + fitBoundsPlacement: OpenSeadragon.Placement.TOP + }, { + tileSource: '/test/data/wide.dzi', + clip: new OpenSeadragon.Rect(0, 0, 100, 250), + fitBounds: new OpenSeadragon.Rect(1, 1, 1, 2), + fitBoundsPlacement: OpenSeadragon.Placement.TOP_LEFT + }]); + }); })();