From e4fca14c33331b8dcde54501f4d253345aa3a18e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 21 Mar 2016 16:11:50 -0400 Subject: [PATCH] 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' + ]); + }); + })();