From b17b9c6f034166fd02d9472ceaf97edc0905d519 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 23 Feb 2016 13:49:11 -0500 Subject: [PATCH 01/78] Fix edge smoothing with png tiles. Fix #854 --- src/tile.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tile.js b/src/tile.js index 9abb14b6..94f58cc2 100644 --- a/src/tile.js +++ b/src/tile.js @@ -280,6 +280,17 @@ $.Tile.prototype = { context.globalAlpha = this.opacity; + if (typeof scale === 'number' && scale !== 1) { + // draw tile at a different scale + position = position.times(scale); + size = size.times(scale); + } + + if (translate instanceof $.Point) { + // shift tile position slightly + position = position.plus(translate); + } + //if we are supposed to be rendering fully opaque rectangle, //ie its done fading or fading is turned off, and if we are drawing //an image with an alpha channel, then the only way @@ -301,17 +312,6 @@ $.Tile.prototype = { // changes as we are rendering the image drawingHandler({context: context, tile: this, rendered: rendered}); - if (typeof scale === 'number' && scale !== 1) { - // draw tile at a different scale - position = position.times(scale); - size = size.times(scale); - } - - if (translate instanceof $.Point) { - // shift tile position slightly - position = position.plus(translate); - } - context.drawImage( rendered.canvas, 0, From 7e3320c167d2b3436b5094c485d86640fcb43161 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 23 Feb 2016 19:37:41 -0500 Subject: [PATCH 02/78] Fix transparent images clearing the images in backgroumd. Fix #849 --- src/tile.js | 9 ++++--- src/tiledimage.js | 9 ++++++- test/modules/multi-image.js | 47 ++++++++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/tile.js b/src/tile.js index 9abb14b6..1144d797 100644 --- a/src/tile.js +++ b/src/tile.js @@ -193,6 +193,11 @@ $.Tile.prototype = { return this.level + "/" + this.x + "_" + this.y; }, + // private + _hasTransparencyChannel: function() { + return this.context2D || this.url.match('.png'); + }, + /** * Renders the tile in an html container. * @function @@ -284,8 +289,7 @@ $.Tile.prototype = { //ie its done fading or fading is turned off, and if we are drawing //an image with an alpha channel, then the only way //to avoid seeing the tile underneath is to clear the rectangle - if (context.globalAlpha === 1 && - (this.context2D || this.url.match('.png'))) { + if (context.globalAlpha === 1 && this._hasTransparencyChannel()) { //clearing only the inside of the rectangle occupied //by the png prevents edge flikering context.clearRect( @@ -294,7 +298,6 @@ $.Tile.prototype = { size.x - 2, size.y - 2 ); - } // This gives the application a chance to make image manipulation diff --git a/src/tiledimage.js b/src/tiledimage.js index 7ad9d7f2..562f1f92 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -650,6 +650,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.raiseEvent('bounds-change'); + }, + + // private + _isBottomItem: function() { + return this.viewer.world.getItemAt(0) === this; } }); @@ -1332,7 +1337,9 @@ function drawTiles( tiledImage, lastDrawn ) { return; } var useSketch = tiledImage.opacity < 1 || - (tiledImage.compositeOperation && tiledImage.compositeOperation !== 'source-over'); + (tiledImage.compositeOperation && + tiledImage.compositeOperation !== 'source-over') || + (!tiledImage._isBottomItem() && tile._hasTransparencyChannel); var sketchScale; var sketchTranslate; diff --git a/test/modules/multi-image.js b/test/modules/multi-image.js index f3710429..cee7867f 100644 --- a/test/modules/multi-image.js +++ b/test/modules/multi-image.js @@ -5,12 +5,12 @@ module( 'Multi-Image', { setup: function() { - $( '
' ).appendTo( "#qunit-fixture" ); + $( '
' ).appendTo( "#qunit-fixture" ); testLog.reset(); viewer = OpenSeadragon( { - id: 'itemsexample', + id: 'example', prefixUrl: '/build/openseadragon/images/', springStiffness: 100 // Faster animation = faster tests }); @@ -21,7 +21,7 @@ } viewer = null; - $( "#itemsexample" ).remove(); + $("#example").remove(); } } ); @@ -208,4 +208,45 @@ viewer.open('/test/data/testpattern.dzi'); }); + asyncTest('Transparent image on top of others', function() { + viewer.open('/test/data/testpattern.dzi'); + + // TODO: replace with fully-loaded event listener when available. + setTimeout(function() { + var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500); + var expectedVal = getPixelValue(imageData, 333, 250); + + viewer.addSimpleImage({ + url: '/test/data/A.png' + }); + + // TODO: replace with fully-loaded event listener when available. + setTimeout(function() { + var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500); + var actualVal = getPixelValue(imageData, 333, 250); + + equal(actualVal.r, expectedVal.r, + 'Red channel should not change when stacking a transparent image'); + equal(actualVal.g, expectedVal.g, + 'Green channel should not change when stacking a transparent image'); + equal(actualVal.b, expectedVal.b, + 'Blue channel should not change when stacking a transparent image'); + equal(actualVal.a, expectedVal.a, + 'Alpha channel should not change when stacking a transparent image'); + + start(); + }, 1000); + }, 1000); + + function getPixelValue(imageData, x, y) { + var offset = x * imageData.width + y; + return { + r: imageData.data[offset], + g: imageData.data[offset + 1], + b: imageData.data[offset + 2], + a: imageData.data[offset + 3] + }; + } + }); + })(); From 510c8c2b97af4dd1edf459a6a5d47b26a8548603 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Wed, 24 Feb 2016 09:27:19 -0800 Subject: [PATCH 03/78] Changelog for #860 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index b9da351a..115db877 100644 --- a/changelog.txt +++ b/changelog.txt @@ -25,6 +25,7 @@ Viewport.contentAspectY have been removed. * Fixed: with scrollToZoom disabled, the viewer caused page scrolling to slow down (#858) * Added Viewer.getOverlayById and Overlay.getBounds functions (#853) * Tiled images with 0 opacity no longer load their tiles or do drawing calculations (#859) +* Fixed issue with edge smoothing with PNG tiles at high zoom (#860) 2.1.0: From d18485844dc5d23aaa45127fcacadd5102165f97 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 24 Feb 2016 19:48:11 -0500 Subject: [PATCH 04/78] Fix crash and improve tests.. --- src/tiledimage.js | 12 +++++------- test/modules/multi-image.js | 30 +++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 562f1f92..53c640c4 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1329,13 +1329,11 @@ function compareTiles( previousBest, tile ) { } function drawTiles( tiledImage, lastDrawn ) { - var i, - tile = lastDrawn[0]; - - if ( tiledImage.opacity <= 0 ) { - drawDebugInfo( tiledImage, lastDrawn ); + if (lastDrawn.length === 0) { return; } + var tile = lastDrawn[0]; + var useSketch = tiledImage.opacity < 1 || (tiledImage.compositeOperation && tiledImage.compositeOperation !== 'source-over') || @@ -1346,7 +1344,7 @@ function drawTiles( tiledImage, lastDrawn ) { var zoom = tiledImage.viewport.getZoom(true); var imageZoom = tiledImage.viewportToImageZoom(zoom); - if (imageZoom > tiledImage.smoothTileEdgesMinZoom && tile) { + if (imageZoom > tiledImage.smoothTileEdgesMinZoom) { // When zoomed in a lot (>100%) the tile edges are visible. // So we have to composite them at ~100% and scale them up together. useSketch = true; @@ -1403,7 +1401,7 @@ function drawTiles( tiledImage, lastDrawn ) { tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch); } - for ( i = lastDrawn.length - 1; i >= 0; i-- ) { + for (var i = lastDrawn.length - 1; i >= 0; i--) { tile = lastDrawn[ i ]; tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate ); tile.beingDrawn = true; diff --git a/test/modules/multi-image.js b/test/modules/multi-image.js index cee7867f..1cfd0515 100644 --- a/test/modules/multi-image.js +++ b/test/modules/multi-image.js @@ -214,7 +214,13 @@ // TODO: replace with fully-loaded event listener when available. setTimeout(function() { var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500); - var expectedVal = getPixelValue(imageData, 333, 250); + // Pixel 250,250 will be in the hole of the A + var expectedVal = getPixelValue(imageData, 250, 250); + + notEqual(expectedVal.r, 0, 'Red channel should not be 0'); + notEqual(expectedVal.g, 0, 'Green channel should not be 0'); + notEqual(expectedVal.b, 0, 'Blue channel should not be 0'); + notEqual(expectedVal.a, 0, 'Alpha channel should not be 0'); viewer.addSimpleImage({ url: '/test/data/A.png' @@ -223,23 +229,29 @@ // TODO: replace with fully-loaded event listener when available. setTimeout(function() { var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500); - var actualVal = getPixelValue(imageData, 333, 250); + var actualVal = getPixelValue(imageData, 250, 250); equal(actualVal.r, expectedVal.r, - 'Red channel should not change when stacking a transparent image'); + 'Red channel should not change in transparent part of the A'); equal(actualVal.g, expectedVal.g, - 'Green channel should not change when stacking a transparent image'); + 'Green channel should not change in transparent part of the A'); equal(actualVal.b, expectedVal.b, - 'Blue channel should not change when stacking a transparent image'); + 'Blue channel should not change in transparent part of the A'); equal(actualVal.a, expectedVal.a, - 'Alpha channel should not change when stacking a transparent image'); + 'Alpha channel should not change in transparent part of the A'); + + var onAVal = getPixelValue(imageData, 333, 250); + equal(onAVal.r, 0, 'Red channel should be null on the A'); + equal(onAVal.g, 0, 'Green channel should be null on the A'); + equal(onAVal.b, 0, 'Blue channel should be null on the A'); + equal(onAVal.a, 255, 'Alpha channel should be 255 on the A'); start(); - }, 1000); - }, 1000); + }, 500); + }, 500); function getPixelValue(imageData, x, y) { - var offset = x * imageData.width + y; + var offset = 4 * (y * imageData.width + x); return { r: imageData.data[offset], g: imageData.data[offset + 1], From 963986d1874bd1a5089a1cb7e94e3a189e3fa7c9 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 24 Feb 2016 19:53:35 -0500 Subject: [PATCH 05/78] Add missing parenthesis. --- src/tiledimage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 53c640c4..9f3acf46 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1337,7 +1337,7 @@ function drawTiles( tiledImage, lastDrawn ) { var useSketch = tiledImage.opacity < 1 || (tiledImage.compositeOperation && tiledImage.compositeOperation !== 'source-over') || - (!tiledImage._isBottomItem() && tile._hasTransparencyChannel); + (!tiledImage._isBottomItem() && tile._hasTransparencyChannel()); var sketchScale; var sketchTranslate; From 0fdebe052c0e9552cd669fb05dec0f6ab31f36a1 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Thu, 25 Feb 2016 09:43:14 -0800 Subject: [PATCH 06/78] Changelog for #861 --- changelog.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index 115db877..db6c8fc9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,12 +1,12 @@ OPENSEADRAGON CHANGELOG ======================= -2.1.1: (in progress) +2.2.0: (in progress) * BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and -Viewport.contentAspectY have been removed. -* DEPRECATION: Viewport.setHomeBounds has been deprecated -* DEPRECATION: the Viewport constructor is now ignoring the contentSize option + Viewport.contentAspectY have been removed. (#846) +* 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) * Fixed issue with reference strip popping up virtual keyboard on mobile devices (#779) * Now supporting rotation in the Rect class (#782) @@ -26,6 +26,7 @@ Viewport.contentAspectY have been removed. * Added Viewer.getOverlayById and Overlay.getBounds functions (#853) * Tiled images with 0 opacity no longer load their tiles or do drawing calculations (#859) * Fixed issue with edge smoothing with PNG tiles at high zoom (#860) +* Fixed: Images with transparency were clearing images layered below them (#861) 2.1.0: From a54d896a45dac120c44a2192bcfb3a0f73337fa7 Mon Sep 17 00:00:00 2001 From: Grant Echols Date: Fri, 4 Mar 2016 11:26:53 -0700 Subject: [PATCH 07/78] Added note about locations being viewport relative for overlays. --- src/viewer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index d96f81c0..8711bd01 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1782,7 +1782,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @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 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or - * rectangle which will be overlayed. + * rectangle which will be overlayed. This is a viewport relative location. * @param {OpenSeadragon.OverlayPlacement} placement - The position of the * viewport which the location coordinates will be treated as relative * to. @@ -1843,7 +1843,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @param {Element|String} element - A reference to an element or an id for * the element which is overlayed. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or - * rectangle which will be overlayed. + * rectangle which will be overlayed. This is a viewport relative location. * @param {OpenSeadragon.OverlayPlacement} placement - The position of the * viewport which the location coordinates will be treated as relative * to. From ef1e5c7d06262b68eb1de9ffaed8e392bda1dbe6 Mon Sep 17 00:00:00 2001 From: rvdb Date: Fri, 4 Mar 2016 22:43:20 +0100 Subject: [PATCH 08/78] removed automatic focus from reference strip, which caused HTML pages to jump unwantedly to the reference strip upon loading --- src/referencestrip.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/referencestrip.js b/src/referencestrip.js index 0743d365..9d88a6a3 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -276,7 +276,6 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp } this.currentPage = page; - $.getElement( element.id + '-displayregion' ).focus(); onStripEnter.call( this, { eventSource: this.innerTracker } ); } }, From 5ca04c56f70e8d958730b5eca4a6e4a9180bfa53 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Mon, 7 Mar 2016 09:46:20 -0800 Subject: [PATCH 09/78] Changelog for #872 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index db6c8fc9..66572c81 100644 --- a/changelog.txt +++ b/changelog.txt @@ -27,6 +27,7 @@ OPENSEADRAGON CHANGELOG * Tiled images with 0 opacity no longer load their tiles or do drawing calculations (#859) * Fixed issue with edge smoothing with PNG tiles at high zoom (#860) * Fixed: Images with transparency were clearing images layered below them (#861) +* Fixed issue causing HTML pages to jump unwantedly to the reference strip upon loading (#872) 2.1.0: From 66f99a1d39ea8b7ccf44c246b39547658ce80607 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 8 Mar 2016 09:58:50 -0800 Subject: [PATCH 10/78] Really no tabIndex if you pass "". --- src/viewer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/viewer.js b/src/viewer.js index 8711bd01..ab782484 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -234,7 +234,9 @@ $.Viewer = function( options ) { style.left = "0px"; }(this.canvas.style)); $.setElementTouchActionNone( this.canvas ); - this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex); + if (options.tabIndex !== "") { + this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex); + } //the container is created through applying the ControlDock constructor above this.container.className = "openseadragon-container"; From 2740792df3144e205dc84f71dd0db1b04479ead3 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 20 Mar 2016 10:04:23 -0400 Subject: [PATCH 11/78] Fix flick gesture with rotation. Fix #869 --- src/viewer.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index d96f81c0..2154a094 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2546,11 +2546,16 @@ function onCanvasDragEnd( event ) { if ( !event.preventDefaultAction && this.viewport ) { gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); - if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed ) { - var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ), - amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ), - center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ), - target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) ); + if (gestureSettings.flickEnabled && + event.speed >= gestureSettings.flickMinSpeed) { + var amplitudeX = gestureSettings.flickMomentum * event.speed * + Math.cos(event.direction); + var amplitudeY = gestureSettings.flickMomentum * event.speed * + Math.sin(event.direction); + var center = this.viewport.pixelFromPoint( + this.viewport.getCenter(true)); + var target = this.viewport.pointFromPixel( + new $.Point(center.x - amplitudeX, center.y - amplitudeY)); if( !this.panHorizontal ) { target.x = center.x; } From 2386900e29d315ef7ee7b350d1b69a23ecf2a8f3 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 20 Mar 2016 11:01:26 -0400 Subject: [PATCH 12/78] Fix drag with panHorizontal/panVertical set to false. --- src/viewer.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index 2154a094..a74292c0 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2542,27 +2542,25 @@ function onCanvasDrag( event ) { } function onCanvasDragEnd( event ) { - var gestureSettings; - - if ( !event.preventDefaultAction && this.viewport ) { - gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); + if (!event.preventDefaultAction && this.viewport) { + var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType); if (gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed) { - var amplitudeX = gestureSettings.flickMomentum * event.speed * - Math.cos(event.direction); - var amplitudeY = gestureSettings.flickMomentum * event.speed * - Math.sin(event.direction); + var amplitudeX = 0; + if (this.panHorizontal) { + amplitudeX = gestureSettings.flickMomentum * event.speed * + Math.cos(event.direction); + } + var amplitudeY = 0; + if (this.panVertical) { + amplitudeY = gestureSettings.flickMomentum * event.speed * + Math.sin(event.direction); + } var center = this.viewport.pixelFromPoint( this.viewport.getCenter(true)); var target = this.viewport.pointFromPixel( new $.Point(center.x - amplitudeX, center.y - amplitudeY)); - if( !this.panHorizontal ) { - target.x = center.x; - } - if( !this.panVertical ) { - target.y = center.y; - } - this.viewport.panTo( target, false ); + this.viewport.panTo(target, false); } this.viewport.applyConstraints(); } @@ -2581,7 +2579,7 @@ function onCanvasDragEnd( event ) { * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - this.raiseEvent( 'canvas-drag-end', { + this.raiseEvent('canvas-drag-end', { tracker: event.eventSource, position: event.position, speed: event.speed, From d3b027bade8497a68eb6202d899d3a2d73b5669c Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 21 Mar 2016 11:27:43 -0400 Subject: [PATCH 13/78] Add addOnceHandler method to EventSource. --- src/eventsource.js | 17 ++++++++++++++++- test/modules/events.js | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/eventsource.js b/src/eventsource.js index e305da25..fc2a28aa 100644 --- a/src/eventsource.js +++ b/src/eventsource.js @@ -56,7 +56,22 @@ $.EventSource = function() { /** @lends OpenSeadragon.EventSource.prototype */ $.EventSource.prototype = { - // TODO: Add a method 'one' which automatically unbinds a listener after the first triggered event that matches. + /** + * Add an event handler to be triggered only once for a given event. + * @function + * @param {String} eventName - Name of event to register. + * @param {OpenSeadragon.EventHandler} handler - Function to call when event + * is triggered. + * @param {Object} [userData=null] - Arbitrary object to be passed unchanged + * to the handler. + */ + addOnceHandler: function(eventName, handler, userData) { + var self = this; + this.addHandler(eventName, function onceHandler(event) { + self.removeHandler(eventName, onceHandler); + handler(event); + }, userData); + }, /** * Add an event handler for a given event. diff --git a/test/modules/events.js b/test/modules/events.js index 62caa072..f6cfeb8b 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -949,6 +949,31 @@ viewer.open( '/test/data/testpattern.dzi' ); } ); + // ---------- + test('EventSource: addOnceHandler', function() { + var eventSource = new OpenSeadragon.EventSource(); + var userData = 'data'; + var eventData = { + foo: 1 + }; + var handlerCalledCount = 0; + eventSource.addOnceHandler('test-event', function(event) { + handlerCalledCount++; + strictEqual(event.foo, eventData.foo, + 'Event data should be transmitted to the event.'); + strictEqual(event.userData, userData, + 'User data should be transmitted to the event.'); + }, userData); + strictEqual(0, handlerCalledCount, + 'Handler should not have been called yet.'); + eventSource.raiseEvent('test-event', eventData); + strictEqual(1, handlerCalledCount, + 'Handler should have been called once.'); + eventSource.raiseEvent('test-event', eventData); + strictEqual(1, handlerCalledCount, + 'Handler should still have been called once.'); + }); + // ---------- asyncTest( 'Viewer: tile-drawing event', function () { var tileDrawing = function ( event ) { From e4fca14c33331b8dcde54501f4d253345aa3a18e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 21 Mar 2016 16:11:50 -0400 Subject: [PATCH 14/78] 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 fddf0fb938a0371dc09fe27e8211fc5a469025bb Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 22 Mar 2016 10:03:52 -0400 Subject: [PATCH 15/78] Use variable instead of function name. --- src/eventsource.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/eventsource.js b/src/eventsource.js index fc2a28aa..6d0d0870 100644 --- a/src/eventsource.js +++ b/src/eventsource.js @@ -67,10 +67,11 @@ $.EventSource.prototype = { */ addOnceHandler: function(eventName, handler, userData) { var self = this; - this.addHandler(eventName, function onceHandler(event) { + var onceHandler = function(event) { self.removeHandler(eventName, onceHandler); handler(event); - }, userData); + }; + this.addHandler(eventName, onceHandler, userData); }, /** From 0f82eed0dbdd85124e23272c732a9c11302e81ed Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 22 Mar 2016 13:54:35 -0400 Subject: [PATCH 16/78] Add times parameter to addOnceHandler. --- src/eventsource.js | 14 +++++++++++--- test/modules/events.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/eventsource.js b/src/eventsource.js index 6d0d0870..e3957d7f 100644 --- a/src/eventsource.js +++ b/src/eventsource.js @@ -57,18 +57,26 @@ $.EventSource = function() { $.EventSource.prototype = { /** - * Add an event handler to be triggered only once for a given event. + * Add an event handler to be triggered only once (or a given number of times) + * for a given event. * @function * @param {String} eventName - Name of event to register. * @param {OpenSeadragon.EventHandler} handler - Function to call when event * is triggered. * @param {Object} [userData=null] - Arbitrary object to be passed unchanged * to the handler. + * @param {Number} [times=1] - The number of times to handle the event + * before removing it. */ - addOnceHandler: function(eventName, handler, userData) { + addOnceHandler: function(eventName, handler, userData, times) { var self = this; + times = times || 1; + var count = 0; var onceHandler = function(event) { - self.removeHandler(eventName, onceHandler); + count++; + if (count === times) { + self.removeHandler(eventName, onceHandler); + } handler(event); }; this.addHandler(eventName, onceHandler, userData); diff --git a/test/modules/events.js b/test/modules/events.js index f6cfeb8b..18f50c37 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -974,6 +974,34 @@ 'Handler should still have been called once.'); }); + // ---------- + test('EventSource: addOnceHandler 2 times', function() { + var eventSource = new OpenSeadragon.EventSource(); + var userData = 'data'; + var eventData = { + foo: 1 + }; + var handlerCalledCount = 0; + eventSource.addOnceHandler('test-event', function(event) { + handlerCalledCount++; + strictEqual(event.foo, eventData.foo, + 'Event data should be transmitted to the event.'); + strictEqual(event.userData, userData, + 'User data should be transmitted to the event.'); + }, userData, 2); + strictEqual(0, handlerCalledCount, + 'Handler should not have been called yet.'); + eventSource.raiseEvent('test-event', eventData); + strictEqual(1, handlerCalledCount, + 'Handler should have been called once.'); + eventSource.raiseEvent('test-event', eventData); + strictEqual(2, handlerCalledCount, + 'Handler should have been called twice.'); + eventSource.raiseEvent('test-event', eventData); + strictEqual(2, handlerCalledCount, + 'Handler should still have been called twice.'); + }); + // ---------- asyncTest( 'Viewer: tile-drawing event', function () { var tileDrawing = function ( event ) { From 9c461824b3b038e855efd4567fa3e6a7d1f29498 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 22 Mar 2016 15:50:48 -0400 Subject: [PATCH 17/78] 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 18/78] 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 19/78] 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 + }]); + }); })(); From d631d975459c9a2ab2d00879e6779e5471a44172 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Thu, 24 Mar 2016 09:50:17 -0700 Subject: [PATCH 20/78] Changelog for #887 and #888 --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 66572c81..abf513ec 100644 --- a/changelog.txt +++ b/changelog.txt @@ -28,6 +28,8 @@ OPENSEADRAGON CHANGELOG * Fixed issue with edge smoothing with PNG tiles at high zoom (#860) * Fixed: Images with transparency were clearing images layered below them (#861) * 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) 2.1.0: From 3e3ce188b1b032d11162a1d08cab160e888faddd Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 25 Mar 2016 16:49:58 -0400 Subject: [PATCH 21/78] 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 22/78] 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 23/78] 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 24/78] 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 25/78] 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 26/78] 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 27/78] 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 28/78] 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 29/78] 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 30/78] 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 31/78] 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 32/78] 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 33/78] 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 34/78] 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 35/78] 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 55dfc146c9c9d2c33d213c333cd417b0a28d3555 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 3 Apr 2016 07:59:10 -0400 Subject: [PATCH 36/78] Remove dead code. --- src/dzitilesource.js | 4 +- src/openseadragon.js | 183 +------------------------------------------ 2 files changed, 4 insertions(+), 183 deletions(-) diff --git a/src/dzitilesource.js b/src/dzitilesource.js index 5d00980f..817f438e 100644 --- a/src/dzitilesource.js +++ b/src/dzitilesource.js @@ -301,7 +301,9 @@ function configureFromXML( tileSource, xmlDoc ){ } else if ( rootName == "Collection" ) { throw new Error( $.getString( "Errors.Dzc" ) ); } else if ( rootName == "Error" ) { - return $._processDZIError( root ); + var messageNode = root.getElementsByTagName("Message")[0]; + var message = messageNode.firstChild.nodeValue; + throw new Error(message); } throw new Error( $.getString( "Errors.Dzi" ) ); diff --git a/src/openseadragon.js b/src/openseadragon.js index 07523516..38c24252 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -2548,185 +2548,4 @@ if (typeof define === 'function' && define.amd) { } } - /** - * @private - * @inner - * @function - * @param {XMLHttpRequest} xhr - * @param {String} tilesUrl - * @deprecated - */ - function processDZIResponse( xhr, tilesUrl ) { - var status, - statusText, - doc = null; - - if ( !xhr ) { - throw new Error( $.getString( "Errors.Security" ) ); - } else if ( xhr.status !== 200 && xhr.status !== 0 ) { - status = xhr.status; - statusText = ( status == 404 ) ? - "Not Found" : - xhr.statusText; - throw new Error( $.getString( "Errors.Status", status, statusText ) ); - } - - if ( xhr.responseXML && xhr.responseXML.documentElement ) { - doc = xhr.responseXML; - } else if ( xhr.responseText ) { - doc = $.parseXml( xhr.responseText ); - } - - return processDZIXml( doc, tilesUrl ); - } - - /** - * @private - * @inner - * @function - * @param {Document} xmlDoc - * @param {String} tilesUrl - * @deprecated - */ - function processDZIXml( xmlDoc, tilesUrl ) { - - if ( !xmlDoc || !xmlDoc.documentElement ) { - throw new Error( $.getString( "Errors.Xml" ) ); - } - - var root = xmlDoc.documentElement, - rootName = root.tagName; - - if ( rootName == "Image" ) { - try { - return processDZI( root, tilesUrl ); - } catch ( e ) { - throw (e instanceof Error) ? - e : - new Error( $.getString("Errors.Dzi") ); - } - } else if ( rootName == "Collection" ) { - throw new Error( $.getString( "Errors.Dzc" ) ); - } else if ( rootName == "Error" ) { - return $._processDZIError( root ); - } - - throw new Error( $.getString( "Errors.Dzi" ) ); - } - - /** - * @private - * @inner - * @function - * @param {Element} imageNode - * @param {String} tilesUrl - * @deprecated - */ - function processDZI( imageNode, tilesUrl ) { - var fileFormat = imageNode.getAttribute( "Format" ), - sizeNode = imageNode.getElementsByTagName( "Size" )[ 0 ], - dispRectNodes = imageNode.getElementsByTagName( "DisplayRect" ), - width = parseInt( sizeNode.getAttribute( "Width" ), 10 ), - height = parseInt( sizeNode.getAttribute( "Height" ), 10 ), - tileSize = parseInt( imageNode.getAttribute( "TileSize" ), 10 ), - tileOverlap = parseInt( imageNode.getAttribute( "Overlap" ), 10 ), - dispRects = [], - dispRectNode, - rectNode, - i; - - if ( !$.imageFormatSupported( fileFormat ) ) { - throw new Error( - $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() ) - ); - } - - for ( i = 0; i < dispRectNodes.length; i++ ) { - dispRectNode = dispRectNodes[ i ]; - rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ]; - - dispRects.push( new $.DisplayRect( - parseInt( rectNode.getAttribute( "X" ), 10 ), - parseInt( rectNode.getAttribute( "Y" ), 10 ), - parseInt( rectNode.getAttribute( "Width" ), 10 ), - parseInt( rectNode.getAttribute( "Height" ), 10 ), - 0, // ignore MinLevel attribute, bug in Deep Zoom Composer - parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 ) - )); - } - return new $.DziTileSource( - width, - height, - tileSize, - tileOverlap, - tilesUrl, - fileFormat, - dispRects - ); - } - - /** - * @private - * @inner - * @function - * @param {Element} imageNode - * @param {String} tilesUrl - * @deprecated - */ - function processDZIJSON( imageData, tilesUrl ) { - var fileFormat = imageData.Format, - sizeData = imageData.Size, - dispRectData = imageData.DisplayRect || [], - width = parseInt( sizeData.Width, 10 ), - height = parseInt( sizeData.Height, 10 ), - tileSize = parseInt( imageData.TileSize, 10 ), - tileOverlap = parseInt( imageData.Overlap, 10 ), - dispRects = [], - rectData, - i; - - if ( !$.imageFormatSupported( fileFormat ) ) { - throw new Error( - $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() ) - ); - } - - for ( i = 0; i < dispRectData.length; i++ ) { - rectData = dispRectData[ i ].Rect; - - dispRects.push( new $.DisplayRect( - parseInt( rectData.X, 10 ), - parseInt( rectData.Y, 10 ), - parseInt( rectData.Width, 10 ), - parseInt( rectData.Height, 10 ), - 0, // ignore MinLevel attribute, bug in Deep Zoom Composer - parseInt( rectData.MaxLevel, 10 ) - )); - } - return new $.DziTileSource( - width, - height, - tileSize, - tileOverlap, - tilesUrl, - fileFormat, - dispRects - ); - } - - /** - * @private - * @inner - * @function - * @param {Document} errorNode - * @throws {Error} - * @deprecated - */ - $._processDZIError = function ( errorNode ) { - var messageNode = errorNode.getElementsByTagName( "Message" )[ 0 ], - message = messageNode.firstChild.nodeValue; - - throw new Error(message); - }; - -}( OpenSeadragon )); +}(OpenSeadragon)); From 96a032164f285f110cdd480dabf6269633378fd7 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 4 Apr 2016 13:59:51 -0400 Subject: [PATCH 37/78] 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 38/78] 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 9e68f6c27b72d611fb8991bc568a9a6223f05612 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 5 Apr 2016 17:50:18 -0400 Subject: [PATCH 39/78] Fix home bounds with clipping. Fix #891 --- src/tiledimage.js | 20 +++++++++ src/viewport.js | 90 +++++++++++++++++++++++++++++++++----- src/world.js | 44 +++++++++++-------- test/modules/tiledimage.js | 5 +-- 4 files changed, 127 insertions(+), 32 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 248f0ece..9d4db782 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -287,6 +287,26 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag return this.getBounds(); }, + /** + * Get the bounds of the displayed part of the tiled image. + * @param {Boolean} [current=false] Pass true for the current location, + * false for the target location. + * @returns {$.Rect} The clipped bounds in viewport coordinates. + */ + getClippedBounds: function(current) { + var bounds = this.getBounds(current); + if (this._clip) { + var ratio = this._worldWidthCurrent / this.source.dimensions.x; + var clip = this._clip.times(ratio); + bounds = new $.Rect( + bounds.x + clip.x, + bounds.y + clip.y, + clip.width, + clip.height); + } + return bounds; + }, + /** * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels. */ diff --git a/src/viewport.js b/src/viewport.js index 63b4dcaf..6f418f13 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -1085,8 +1085,18 @@ $.Viewport.prototype = { return this.viewportToImageCoordinates(viewerX.x, viewerX.y); } - if (this.viewer && this.viewer.world.getItemCount() > 1) { - $.console.error('[Viewport.viewportToImageCoordinates] is not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.'); + if (this.viewer) { + var count = this.viewer.world.getItemCount(); + if (count > 1) { + $.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' + + 'with multi-image; use TiledImage.viewportToImageCoordinates instead.'); + } else if (count === 1) { + // It is better to use TiledImage.viewportToImageCoordinates + // because this._contentBoundsNoRotate can not be relied on + // with clipping. + var item = this.viewer.world.getItemAt(0); + return item.viewportToImageCoordinates(viewerX, viewerY, true); + } } return this._viewportToImageDelta( @@ -1119,8 +1129,18 @@ $.Viewport.prototype = { return this.imageToViewportCoordinates(imageX.x, imageX.y); } - if (this.viewer && this.viewer.world.getItemCount() > 1) { - $.console.error('[Viewport.imageToViewportCoordinates] is not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.'); + if (this.viewer) { + var count = this.viewer.world.getItemCount(); + if (count > 1) { + $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' + + 'with multi-image; use TiledImage.imageToViewportCoordinates instead.'); + } else if (count === 1) { + // It is better to use TiledImage.viewportToImageCoordinates + // because this._contentBoundsNoRotate can not be relied on + // with clipping. + var item = this.viewer.world.getItemAt(0); + return item.imageToViewportCoordinates(imageX, imageY, true); + } } var point = this._imageToViewportDelta(imageX, imageY); @@ -1150,6 +1170,21 @@ $.Viewport.prototype = { rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight); } + if (this.viewer) { + var count = this.viewer.world.getItemCount(); + if (count > 1) { + $.console.error('[Viewport.imageToViewportRectangle] is not accurate ' + + 'with multi-image; use TiledImage.imageToViewportRectangle instead.'); + } else if (count === 1) { + // It is better to use TiledImage.imageToViewportRectangle + // because this._contentBoundsNoRotate can not be relied on + // with clipping. + var item = this.viewer.world.getItemAt(0); + return item.imageToViewportRectangle( + imageX, imageY, pixelWidth, pixelHeight, true); + } + } + var coordA = this.imageToViewportCoordinates(rect.x, rect.y); var coordB = this._imageToViewportDelta(rect.width, rect.height); return new $.Rect( @@ -1183,6 +1218,21 @@ $.Viewport.prototype = { rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight); } + if (this.viewer) { + var count = this.viewer.world.getItemCount(); + if (count > 1) { + $.console.error('[Viewport.viewportToImageRectangle] is not accurate ' + + 'with multi-image; use TiledImage.viewportToImageRectangle instead.'); + } else if (count === 1) { + // It is better to use TiledImage.viewportToImageCoordinates + // because this._contentBoundsNoRotate can not be relied on + // with clipping. + var item = this.viewer.world.getItemAt(0); + return item.viewportToImageRectangle( + viewerX, viewerY, pointWidth, pointHeight, true); + } + } + var coordA = this.viewportToImageCoordinates(rect.x, rect.y); var coordB = this._viewportToImageDelta(rect.width, rect.height); return new $.Rect( @@ -1296,9 +1346,19 @@ $.Viewport.prototype = { * target zoom. * @returns {Number} imageZoom The image zoom */ - viewportToImageZoom: function( viewportZoom ) { - if (this.viewer && this.viewer.world.getItemCount() > 1) { - $.console.error('[Viewport.viewportToImageZoom] is not accurate with multi-image.'); + viewportToImageZoom: function(viewportZoom) { + if (this.viewer) { + var count = this.viewer.world.getItemCount(); + if (count > 1) { + $.console.error('[Viewport.viewportToImageZoom] is not ' + + 'accurate with multi-image.'); + } else if (count === 1) { + // It is better to use TiledImage.viewportToImageZoom + // because this._contentBoundsNoRotate can not be relied on + // with clipping. + var item = this.viewer.world.getItemAt(0); + return item.viewportToImageZoom(viewportZoom); + } } var imageWidth = this._contentSizeNoRotate.x; @@ -1320,9 +1380,19 @@ $.Viewport.prototype = { * target zoom. * @returns {Number} viewportZoom The viewport zoom */ - imageToViewportZoom: function( imageZoom ) { - if (this.viewer && this.viewer.world.getItemCount() > 1) { - $.console.error('[Viewport.imageToViewportZoom] is not accurate with multi-image.'); + imageToViewportZoom: function(imageZoom) { + if (this.viewer) { + var count = this.viewer.world.getItemCount(); + if (count > 1) { + $.console.error('[Viewport.imageToViewportZoom] is not accurate ' + + 'with multi-image.'); + } else if (count === 1) { + // It is better to use TiledImage.imageToViewportZoom + // because this._contentBoundsNoRotate can not be relied on + // with clipping. + var item = this.viewer.world.getItemAt(0); + return item.imageToViewportZoom(imageZoom); + } } var imageWidth = this._contentSizeNoRotate.x; diff --git a/src/world.js b/src/world.js index 5597f0f8..07e99b81 100644 --- a/src/world.js +++ b/src/world.js @@ -375,34 +375,40 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W var oldContentSize = this._contentSize ? this._contentSize.clone() : null; var oldContentFactor = this._contentFactor || 0; - if ( !this._items.length ) { + if (!this._items.length) { this._homeBounds = new $.Rect(0, 0, 1, 1); this._contentSize = new $.Point(1, 1); this._contentFactor = 1; } else { - var bounds = this._items[0].getBounds(); - this._contentFactor = this._items[0].getContentSize().x / bounds.width; - var left = bounds.x; - var top = bounds.y; - var right = bounds.x + bounds.width; - var bottom = bounds.y + bounds.height; - var box; - for ( var i = 1; i < this._items.length; i++ ) { - box = this._items[i].getBounds(); - this._contentFactor = Math.max(this._contentFactor, this._items[i].getContentSize().x / box.width); - left = Math.min( left, box.x ); - top = Math.min( top, box.y ); - right = Math.max( right, box.x + box.width ); - bottom = Math.max( bottom, box.y + box.height ); + var item = this._items[0]; + var bounds = item.getBounds(); + this._contentFactor = item.getContentSize().x / bounds.width; + var clippedBounds = item.getClippedBounds(); + var left = clippedBounds.x; + var top = clippedBounds.y; + var right = clippedBounds.x + clippedBounds.width; + var bottom = clippedBounds.y + clippedBounds.height; + for (var i = 1; i < this._items.length; i++) { + item = this._items[i]; + bounds = item.getBounds(); + this._contentFactor = Math.max(this._contentFactor, + item.getContentSize().x / bounds.width); + clippedBounds = item.getClippedBounds(); + left = Math.min(left, clippedBounds.x); + top = Math.min(top, clippedBounds.y); + right = Math.max(right, clippedBounds.x + clippedBounds.width); + bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height); } - this._homeBounds = new $.Rect( left, top, right - left, bottom - top ); - this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor, + this._homeBounds = new $.Rect(left, top, right - left, bottom - top); + this._contentSize = new $.Point( + this._homeBounds.width * this._contentFactor, this._homeBounds.height * this._contentFactor); } - if (this._contentFactor !== oldContentFactor || !this._homeBounds.equals(oldHomeBounds) || - !this._contentSize.equals(oldContentSize)) { + if (this._contentFactor !== oldContentFactor || + !this._homeBounds.equals(oldHomeBounds) || + !this._contentSize.equals(oldContentSize)) { /** * Raised when the home bounds or content factor change. * @event metrics-change diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index f281d076..405f0dfc 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -206,9 +206,8 @@ propEqual(image.getClip(), clip, 'clip is set correctly'); Util.spyOnce(viewer.drawer, 'setClip', function(rect) { - ok(true, 'drawer.setClip is called'); - var pixelRatio = viewer.viewport.getContainerSize().x / image.getContentSize().x; - var canvasClip = clip.times(pixelRatio * OpenSeadragon.pixelDensityRatio); + var homeBounds = viewer.viewport.getHomeBounds(); + var canvasClip = viewer.viewport.viewportToViewerElementRectangle(homeBounds); propEqual(rect, canvasClip, 'clipping to correct rect'); start(); }); From bd4cabaec2f0e965e6601b44af1ebf14aa6394bf Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 5 Apr 2016 20:00:35 -0400 Subject: [PATCH 40/78] Fix JSDoc. --- src/openseadragon.js | 8 +------- src/viewport.js | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 07523516..7b51ee7f 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -96,11 +96,6 @@ * */ -/** - * @module OpenSeadragon - * - */ - /** * @namespace OpenSeadragon * @@ -691,8 +686,7 @@ * This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all * combinations of out-of-the-box configurable features. * - * @function OpenSeadragon - * @memberof module:OpenSeadragon + * @function * @param {OpenSeadragon.Options} options - Viewer options. * @returns {OpenSeadragon.Viewer} */ diff --git a/src/viewport.js b/src/viewport.js index 63b4dcaf..12dd958d 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -317,8 +317,8 @@ $.Viewport.prototype = { }, /** - * @function * The margins push the "home" region in from the sides by the specified amounts. + * @function * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom. */ getMargins: function() { @@ -326,8 +326,8 @@ $.Viewport.prototype = { }, /** - * @function * The margins push the "home" region in from the sides by the specified amounts. + * @function * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom. */ setMargins: function(margins) { From 81f439d43070fba72010b0ce2e20595aa8fde5c2 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 6 Apr 2016 09:10:51 -0400 Subject: [PATCH 41/78] 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 42/78] 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 || From 5ebf84a580c4af1b6e84ecf523911e481818c8ac Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 9 Apr 2016 10:14:09 -0400 Subject: [PATCH 43/78] Fix typo in doc. --- src/tilesource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tilesource.js b/src/tilesource.js index 290a5758..03854bbc 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -544,7 +544,7 @@ $.TileSource.prototype = { /** * Responsible for retriving the url which will return an image for the - * region speified by the given x, y, and level components. + * region specified by the given x, y, and level components. * This method is not implemented by this class other than to throw an Error * announcing you have to implement it. Because of the variety of tile * server technologies, and various specifications for building image From 4fa7ed159000db58d9e9cf15b177bbd1c3ce1353 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 9 Apr 2016 10:15:02 -0400 Subject: [PATCH 44/78] Adapt doc to new version of JSDoc. --- src/openseadragon.js | 17 +---------------- src/viewer.js | 17 +++++++++++------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 7b51ee7f..cee2d9e3 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -82,23 +82,9 @@ */ -/** - * @version <%= pkg.name %> <%= pkg.version %> - * - * @file - *

OpenSeadragon - Javascript Deep Zooming

- *

- * OpenSeadragon provides an html interface for creating - * deep zoom user interfaces. The simplest examples include deep - * zoom for large resolution images, and complex examples include - * zoomable map interfaces driven by SVG files. - *

- * - */ - /** * @namespace OpenSeadragon - * + * @version <%= pkg.name %> <%= pkg.version %> * @classdesc The root namespace for OpenSeadragon. All utility methods * and classes are defined on or below this namespace. * @@ -686,7 +672,6 @@ * This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all * combinations of out-of-the-box configurable features. * - * @function * @param {OpenSeadragon.Options} options - Viewer options. * @returns {OpenSeadragon.Viewer} */ diff --git a/src/viewer.js b/src/viewer.js index d3cce4b8..b647ee41 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -40,14 +40,19 @@ var nextHash = 1; /** * - * The main point of entry into creating a zoomable image on the page. - * + * The main point of entry into creating a zoomable image on the page.
+ *
* We have provided an idiomatic javascript constructor which takes - * a single object, but still support the legacy positional arguments. - * + * a single object, but still support the legacy positional arguments.
+ *
* The options below are given in order that they appeared in the constructor - * as arguments and we translate a positional call into an idiomatic call. - * + * as arguments and we translate a positional call into an idiomatic call.
+ *
+ * To create a viewer, you can use either of this methods:
+ *
    + *
  • var viewer = new OpenSeadragon.Viewer(options);
  • + *
  • var viewer = OpenSeadragon(options);
  • + *
* @class Viewer * @classdesc The main OpenSeadragon viewer class. * From e436fc93fd2e66c1fe2687d4c41faf7a2b4fee66 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 9 Apr 2016 11:23:59 -0400 Subject: [PATCH 45/78] Fix test. --- test/modules/tiledimage.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 405f0dfc..49cd71d8 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -207,8 +207,17 @@ Util.spyOnce(viewer.drawer, 'setClip', function(rect) { var homeBounds = viewer.viewport.getHomeBounds(); - var canvasClip = viewer.viewport.viewportToViewerElementRectangle(homeBounds); - propEqual(rect, canvasClip, 'clipping to correct rect'); + var canvasClip = viewer.viewport + .viewportToViewerElementRectangle(homeBounds); + var precision = 0.00000001; + Util.assessNumericValue(rect.x, canvasClip.x, precision, + 'clipping x should be ' + canvasClip.x); + Util.assessNumericValue(rect.y, canvasClip.y, precision, + 'clipping y should be ' + canvasClip.y); + Util.assessNumericValue(rect.width, canvasClip.width, precision, + 'clipping width should be ' + canvasClip.width); + Util.assessNumericValue(rect.height, canvasClip.height, precision, + 'clipping height should be ' + canvasClip.height); start(); }); }); From 686176f82159e44b4496b2d2f48f5d2c4957dab8 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 9 Apr 2016 11:37:05 -0400 Subject: [PATCH 46/78] Add TiledImage.getClippedBounds test. --- test/modules/tiledimage.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 49cd71d8..614d28fd 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -228,6 +228,39 @@ }); }); + asyncTest('getClipBounds', function() { + var clip = new OpenSeadragon.Rect(100, 200, 800, 500); + + viewer.addHandler('open', function() { + var image = viewer.world.getItemAt(0); + var bounds = image.getClippedBounds(); + var expectedBounds = new OpenSeadragon.Rect(1.2, 1.4, 1.6, 1); + propEqual(bounds, expectedBounds, + 'getClipBounds should take clipping into account.'); + + image = viewer.world.getItemAt(1); + bounds = image.getClippedBounds(); + expectedBounds = new OpenSeadragon.Rect(1, 2, 2, 2); + propEqual(bounds, expectedBounds, + 'getClipBounds should work when no clipping set.'); + + start(); + }); + + viewer.open([{ + tileSource: '/test/data/testpattern.dzi', + clip: clip, + x: 1, + y: 1, + width: 2 + }, { + tileSource: '/test/data/testpattern.dzi', + x: 1, + y: 2, + width: 2 + }]); + }); + // ---------- asyncTest('opacity', function() { From b8c87ddb61ea0943bc9a54c8019bc53cbcaa2db4 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 9 Apr 2016 11:56:34 -0400 Subject: [PATCH 47/78] Use Util.assertRectangleEquals --- test/modules/tiledimage.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 614d28fd..d7a46aef 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -210,14 +210,8 @@ var canvasClip = viewer.viewport .viewportToViewerElementRectangle(homeBounds); var precision = 0.00000001; - Util.assessNumericValue(rect.x, canvasClip.x, precision, - 'clipping x should be ' + canvasClip.x); - Util.assessNumericValue(rect.y, canvasClip.y, precision, - 'clipping y should be ' + canvasClip.y); - Util.assessNumericValue(rect.width, canvasClip.width, precision, - 'clipping width should be ' + canvasClip.width); - Util.assessNumericValue(rect.height, canvasClip.height, precision, - 'clipping height should be ' + canvasClip.height); + Util.assertRectangleEquals(rect, canvasClip, precision, + 'clipping should be ' + canvasClip); start(); }); }); From e0e6ce9b65662528fb743c8edc4adf0ce751599c Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 9 Apr 2016 18:13:37 -0400 Subject: [PATCH 48/78] Add unit tests for home bounds with clip. --- test/modules/viewport.js | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/modules/viewport.js b/test/modules/viewport.js index 70e980ed..0b3ca020 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -238,6 +238,57 @@ viewer.open(DZI_PATH); }); + asyncTest('getHomeBoundsWithMultiImages', function() { + function openHandler() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + Util.assertRectangleEquals( + new OpenSeadragon.Rect(0, 0, 4, 4), + viewport.getHomeBounds(), + 0.00000001, + "Test getHomeBoundsWithMultiImages"); + start(); + } + viewer.addHandler('open', openHandler); + viewer.open([{ + tileSource: DZI_PATH, + x: 0, + y: 0, + width: 2 + }, { + tileSource: DZI_PATH, + x: 3, + y: 3, + width: 1 + }]); + }); + + asyncTest('getHomeBoundsWithMultiImagesAndClipping', function() { + function openHandler() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + Util.assertRectangleEquals( + new OpenSeadragon.Rect(1, 1, 4, 4), + viewport.getHomeBounds(), + 0.00000001, + "Test getHomeBoundsWithMultiImagesAndClipping"); + start(); + } + viewer.addHandler('open', openHandler); + viewer.open([{ + tileSource: DZI_PATH, + x: 0, + y: 0, + width: 2, + clip: new OpenSeadragon.Rect(500, 500, 500, 500) + }, { + tileSource: DZI_PATH, + x: 4, + y: 4, + width: 1 + }]); + }); + asyncTest('getHomeZoom', function() { reopenViewerHelper({ property: 'defaultZoomLevel', From 4bf7b629398b0e79da8efc247396facb87811b61 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 10 Apr 2016 19:01:30 -0400 Subject: [PATCH 49/78] Fix enums doc. --- src/overlay.js | 7 +++++-- src/placement.js | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/overlay.js b/src/overlay.js index 76eb347c..25cc50d2 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -42,6 +42,7 @@ * @member OverlayPlacement * @memberof OpenSeadragon * @static + * @readonly * @type {Object} * @property {Number} CENTER * @property {Number} TOP_LEFT @@ -57,8 +58,10 @@ /** * An enumeration of possible ways to handle overlays rotation + * @member OverlayRotationMode * @memberOf OpenSeadragon * @static + * @readonly * @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. @@ -66,11 +69,11 @@ * taking the size of the bounding box of the rotated bounds. * Only valid for overlays with Rect location and scalable in both directions. */ - $.OverlayRotationMode = { + $.OverlayRotationMode = $.freezeObject({ NO_ROTATION: 1, EXACT: 2, BOUNDING_BOX: 3 - }; + }); /** * @class Overlay diff --git a/src/placement.js b/src/placement.js index a90bf5da..561d5daf 100644 --- a/src/placement.js +++ b/src/placement.js @@ -35,8 +35,10 @@ /** * An enumeration of positions to anchor an element. + * @member Placement * @memberOf OpenSeadragon * @static + * @readonly * @property {OpenSeadragon.Placement} CENTER * @property {OpenSeadragon.Placement} TOP_LEFT * @property {OpenSeadragon.Placement} TOP From b1a0abd1041682a9d852c48139e63981f3da2aa5 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 19 Apr 2016 18:13:12 -0400 Subject: [PATCH 50/78] Add this.viewer test. --- src/viewport.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/viewport.js b/src/viewport.js index a2a9cc26..5095a9d5 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -341,7 +341,9 @@ $.Viewport.prototype = { }, margins); this._updateContainerInnerSize(); - this.viewer.forceRedraw(); + if (this.viewer) { + this.viewer.forceRedraw(); + } }, /** From 3775a877e2a3571c6b20f20edf1d5ff930140cdd Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 21 Apr 2016 10:02:02 -0400 Subject: [PATCH 51/78] Remove trailing spaces. --- src/tilesource.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tilesource.js b/src/tilesource.js index 03854bbc..48941848 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -190,7 +190,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve this.aspectRatio = ( options.width && options.height ) ? ( options.width / options.height ) : 1; this.dimensions = new $.Point( options.width, options.height ); - + if ( this.tileSize ){ this._tileWidth = this._tileHeight = this.tileSize; delete this.tileSize; @@ -212,7 +212,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve this._tileHeight = 0; } } - + this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0; this.minLevel = options.minLevel ? options.minLevel : 0; this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ? @@ -240,7 +240,7 @@ $.TileSource.prototype = { ); return this._tileWidth; }, - + /** * Return the tileWidth for a given level. * Subclasses should override this if tileWidth can be different at different levels @@ -331,7 +331,7 @@ $.TileSource.prototype = { Math.floor( rect.x / this.getTileWidth(i) ), Math.floor( rect.y / this.getTileHeight(i) ) ); - + if( tiles.x + 1 >= tilesPerSide.x && tiles.y + 1 >= tilesPerSide.y ){ break; } From f8de9b33b023bb0dff51dc69cd37851f76e04108 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 21 Apr 2016 10:31:49 -0400 Subject: [PATCH 52/78] Fix getScaleForEdgeSmoothing with image tile source. --- src/tile.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/tile.js b/src/tile.js index 56fe1824..d9c391ce 100644 --- a/src/tile.js +++ b/src/tile.js @@ -336,15 +336,18 @@ $.Tile.prototype = { * @return {Float} */ getScaleForEdgeSmoothing: function() { - if (!this.cacheImageRecord) { + var context; + if (this.cacheImageRecord) { + context = this.cacheImageRecord.getRenderedContext(); + } else if (this.context2D) { + context = this.context2D; + } else { $.console.warn( '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached', this.toString()); return 1; } - - var rendered = this.cacheImageRecord.getRenderedContext(); - return rendered.canvas.width / this.size.times($.pixelDensityRatio).x; + return context.canvas.width / (this.size.x * $.pixelDensityRatio); }, /** From 65a95d4a4988a33f9cb6f27cee5bdabec4cc4afb Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 21 Apr 2016 10:57:39 -0400 Subject: [PATCH 53/78] Add asserts on this.viewer. --- src/viewport.js | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 5095a9d5..5a70de16 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -1134,7 +1134,7 @@ $.Viewport.prototype = { if (this.viewer) { var count = this.viewer.world.getItemCount(); if (count > 1) { - $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' + + $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' + 'with multi-image; use TiledImage.imageToViewportCoordinates instead.'); } else if (count === 1) { // It is better to use TiledImage.viewportToImageCoordinates @@ -1276,10 +1276,12 @@ $.Viewport.prototype = { * @param {OpenSeadragon.Point} pixel * @returns {OpenSeadragon.Point} */ - windowToImageCoordinates: function( pixel ) { + windowToImageCoordinates: function(pixel) { + $.console.assert(this.viewer, + "[Viewport.windowToImageCoordinates] the viewport must have a viewer."); var viewerCoordinates = pixel.minus( - OpenSeadragon.getElementPosition( this.viewer.element )); - return this.viewerElementToImageCoordinates( viewerCoordinates ); + $.getElementPosition(this.viewer.element)); + return this.viewerElementToImageCoordinates(viewerCoordinates); }, /** @@ -1288,10 +1290,12 @@ $.Viewport.prototype = { * @param {OpenSeadragon.Point} pixel * @returns {OpenSeadragon.Point} */ - imageToWindowCoordinates: function( pixel ) { - var viewerCoordinates = this.imageToViewerElementCoordinates( pixel ); + imageToWindowCoordinates: function(pixel) { + $.console.assert(this.viewer, + "[Viewport.imageToWindowCoordinates] the viewport must have a viewer."); + var viewerCoordinates = this.imageToViewerElementCoordinates(pixel); return viewerCoordinates.plus( - OpenSeadragon.getElementPosition( this.viewer.element )); + $.getElementPosition(this.viewer.element)); }, /** @@ -1347,10 +1351,12 @@ $.Viewport.prototype = { * @param {OpenSeadragon.Point} pixel * @returns {OpenSeadragon.Point} */ - windowToViewportCoordinates: function( pixel ) { + windowToViewportCoordinates: function(pixel) { + $.console.assert(this.viewer, + "[Viewport.windowToViewportCoordinates] the viewport must have a viewer."); var viewerCoordinates = pixel.minus( - OpenSeadragon.getElementPosition( this.viewer.element )); - return this.viewerElementToViewportCoordinates( viewerCoordinates ); + $.getElementPosition(this.viewer.element)); + return this.viewerElementToViewportCoordinates(viewerCoordinates); }, /** @@ -1358,10 +1364,12 @@ $.Viewport.prototype = { * @param {OpenSeadragon.Point} point * @returns {OpenSeadragon.Point} */ - viewportToWindowCoordinates: function( point ) { - var viewerCoordinates = this.viewportToViewerElementCoordinates( point ); + viewportToWindowCoordinates: function(point) { + $.console.assert(this.viewer, + "[Viewport.viewportToWindowCoordinates] the viewport must have a viewer."); + var viewerCoordinates = this.viewportToViewerElementCoordinates(point); return viewerCoordinates.plus( - OpenSeadragon.getElementPosition( this.viewer.element )); + $.getElementPosition(this.viewer.element)); }, /** @@ -1380,7 +1388,7 @@ $.Viewport.prototype = { if (this.viewer) { var count = this.viewer.world.getItemCount(); if (count > 1) { - $.console.error('[Viewport.viewportToImageZoom] is not ' + + $.console.error('[Viewport.viewportToImageZoom] is not ' + 'accurate with multi-image.'); } else if (count === 1) { // It is better to use TiledImage.viewportToImageZoom From 79977b09a0162896d4a3bdb703fa78ec947dfe21 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 21 Apr 2016 16:06:07 -0400 Subject: [PATCH 54/78] Fix Viewport.update with zoomPoint. --- src/spring.js | 9 +++++++++ src/viewport.js | 34 +++++++++++++++++----------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/spring.js b/src/spring.js index e580b2e6..71c94d06 100644 --- a/src/spring.js +++ b/src/spring.js @@ -234,6 +234,15 @@ $.Spring.prototype = { } else { this.current.value = currentValue; } + }, + + /** + * Returns whether the spring is at the target value + * @function + * @returns {Boolean} True if at target value, false otherwise + */ + isAtTargetValue: function() { + return this.current.value === this.target.value; } }; diff --git a/src/viewport.js b/src/viewport.js index 5dc60041..c8dcd31e 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -888,37 +888,37 @@ $.Viewport.prototype = { }, /** + * Update the zoom and center (X and Y) springs. * @function + * @returns {Boolean} True if any change has been made, false otherwise. */ update: function() { - var oldZoomPixel, - newZoomPixel, - deltaZoomPixels, - deltaZoomPoints; if (this.zoomPoint) { - oldZoomPixel = this.pixelFromPoint( this.zoomPoint, true ); - } + var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true); + this.zoomSpring.update(); + var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true); - this.zoomSpring.update(); + var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel); + var deltaZoomPoints = this.deltaPointsFromPixels( + deltaZoomPixels, true); - if (this.zoomPoint && this.zoomSpring.current.value != this._oldZoom) { - newZoomPixel = this.pixelFromPoint( this.zoomPoint, true ); - deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); - deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true ); + this.centerSpringX.shiftBy(deltaZoomPoints.x); + this.centerSpringY.shiftBy(deltaZoomPoints.y); - this.centerSpringX.shiftBy( deltaZoomPoints.x ); - this.centerSpringY.shiftBy( deltaZoomPoints.y ); + if (this.zoomSpring.isAtTargetValue()) { + this.zoomPoint = null; + } } else { - this.zoomPoint = null; + this.zoomSpring.update(); } this.centerSpringX.update(); this.centerSpringY.update(); - var changed = this.centerSpringX.current.value != this._oldCenterX || - this.centerSpringY.current.value != this._oldCenterY || - this.zoomSpring.current.value != this._oldZoom; + var changed = this.centerSpringX.current.value !== this._oldCenterX || + this.centerSpringY.current.value !== this._oldCenterY || + this.zoomSpring.current.value !== this._oldZoom; this._oldCenterX = this.centerSpringX.current.value; this._oldCenterY = this.centerSpringY.current.value; From b2dbf35dcb9e7888f056924e82fe3c248d1fbb01 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 22 Apr 2016 11:09:27 -0700 Subject: [PATCH 55/78] Changelog for #910 and #923 --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 8829d474..8f72af41 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,6 +38,8 @@ OPENSEADRAGON CHANGELOG * Added TiledImage.fitBounds method (#888) * 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) +* Viewport.goHome() now takes clipping into account (#910) +* Improved zoom to point (#923) 2.1.0: From 3106d8f85b49a2e74e00bbe614b0a47428f79fb9 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 23 Apr 2016 08:29:32 -0400 Subject: [PATCH 56/78] Fix viewport.fitBounds tests. --- test/modules/viewport.js | 51 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/test/modules/viewport.js b/test/modules/viewport.js index 0b3ca020..e40f82be 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -405,26 +405,7 @@ viewer.open(DZI_PATH); }); - asyncTest('fitBounds', function(){ - var openHandler = function(event) { - viewer.removeHandler('open', openHandler); - var viewport = viewer.viewport; - - for(var i = 0; i < testRects.length; i++){ - var rect = testRects[i].times(viewport.getContainerSize()); - viewport.fitBounds(rect, true); - propEqual( - viewport.getBounds(), - rect, - "Fit bounds correctly." - ); - } - start(); - }; - viewer.addHandler('open', openHandler); - viewer.open(DZI_PATH); - }); - + // Fit bounds tests var testRectsFitBounds = [ new OpenSeadragon.Rect(0, -0.75, 0.5, 1), new OpenSeadragon.Rect(0.5, 0, 0.5, 0.8), @@ -433,12 +414,39 @@ ]; var expectedRectsFitBounds = [ + new OpenSeadragon.Rect(-0.25, -0.75, 1, 1), + new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8), + new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5), + new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5) + ]; + + var expectedRectsFitBoundsWithConstraints = [ new OpenSeadragon.Rect(-0.25, -0.5, 1, 1), new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8), new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5), new OpenSeadragon.Rect(-0.25, -0.25, 0.5, 0.5) ]; + asyncTest('fitBounds', function(){ + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + for(var i = 0; i < testRectsFitBounds.length; i++){ + var rect = testRectsFitBounds[i]; + viewport.fitBounds(rect, true); + propEqual( + viewport.getBounds(), + expectedRectsFitBounds[i], + "Fit bounds correctly." + ); + } + start(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + asyncTest('fitBoundsWithConstraints', function(){ var openHandler = function(event) { viewer.removeHandler('open', openHandler); @@ -450,7 +458,7 @@ viewport.fitBoundsWithConstraints(rect, true); propEqual( viewport.getBounds(), - expectedRectsFitBounds[i], + expectedRectsFitBoundsWithConstraints[i], "Fit bounds correctly." ); } @@ -491,6 +499,7 @@ viewer.addHandler('open', openHandler); viewer.open(WIDE_PATH); }); + // End fitBounds tests. asyncTest('panBy', function(){ var openHandler = function(event) { From 684029bc791a8a3bce2bb3e78f7b3f7f093bac1e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 27 Apr 2016 11:08:44 -0400 Subject: [PATCH 57/78] Optimize sketch canvas clearing and blending. --- src/drawer.js | 107 +++++++++++++++++++++++++++++++--------------- src/rectangle.js | 14 ++++++ src/tile.js | 2 +- src/tiledimage.js | 19 ++++++-- 4 files changed, 103 insertions(+), 39 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index 1677dc59..661663d1 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -259,13 +259,17 @@ $.Drawer.prototype = { } }, - _clear: function ( useSketch ) { - if ( !this.useCanvas ) { + _clear: function (useSketch, bounds) { + if (!this.useCanvas) { return; } - var context = this._getContext( useSketch ); - var canvas = context.canvas; - context.clearRect( 0, 0, canvas.width, canvas.height ); + var context = this._getContext(useSketch); + if (bounds) { + context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height); + } else { + var canvas = context.canvas; + context.clearRect(0, 0, canvas.width, canvas.height); + } }, /** @@ -382,47 +386,80 @@ $.Drawer.prototype = { /** * Blends the sketch canvas in the main canvas. - * @param {Float} opacity The opacity of the blending. - * @param {Float} [scale=1] The scale at which tiles were drawn on the sketch. Default is 1. - * Use scale to draw at a lower scale and then enlarge onto the main canvas. - * @param {OpenSeadragon.Point} [translate] A translation vector that was used to draw the tiles - * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values. - * @returns {undefined} + * @param {Object} options The options + * @param {Float} options.opacity The opacity of the blending. + * @param {Float} [options.scale=1] The scale at which tiles were drawn on + * the sketch. Default is 1. + * Use scale to draw at a lower scale and then enlarge onto the main canvas. + * @param {OpenSeadragon.Point} [options.translate] A translation vector + * that was used to draw the tiles + * @param {String} [options.compositeOperation] - How the image is + * composited onto other images; see compositeOperation in + * {@link OpenSeadragon.Options} for possible values. + * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch + * canvas to blend in the main canvas. If specified, options.scale and + * options.translate get ignored. */ blendSketch: function(opacity, scale, translate, compositeOperation) { + var options = opacity; + if (!$.isPlainObject(options)) { + options = { + opacity: opacity, + scale: scale, + translate: translate, + compositeOperation: compositeOperation + }; + } if (!this.useCanvas || !this.sketchCanvas) { return; } - scale = scale || 1; - var position = translate instanceof $.Point ? - translate : - new $.Point(0, 0); - - var widthExt = 0; - var heightExt = 0; - if (translate) { - var widthDiff = this.sketchCanvas.width - this.canvas.width; - var heightDiff = this.sketchCanvas.height - this.canvas.height; - widthExt = Math.round(widthDiff / 2); - heightExt = Math.round(heightDiff / 2); - } + opacity = options.opacity; + compositeOperation = options.compositeOperation; + var bounds = options.bounds; this.context.save(); this.context.globalAlpha = opacity; if (compositeOperation) { this.context.globalCompositeOperation = compositeOperation; } - this.context.drawImage( - this.sketchCanvas, - position.x - widthExt * scale, - position.y - heightExt * scale, - (this.canvas.width + 2 * widthExt) * scale, - (this.canvas.height + 2 * heightExt) * scale, - -widthExt, - -heightExt, - this.canvas.width + 2 * widthExt, - this.canvas.height + 2 * heightExt - ); + if (bounds) { + this.context.drawImage( + this.sketchCanvas, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + bounds.x, + bounds.y, + bounds.width, + bounds.height + ); + } else { + scale = options.scale || 1; + translate = options.translate; + var position = translate instanceof $.Point ? + translate : new $.Point(0, 0); + + var widthExt = 0; + var heightExt = 0; + if (translate) { + var widthDiff = this.sketchCanvas.width - this.canvas.width; + var heightDiff = this.sketchCanvas.height - this.canvas.height; + widthExt = Math.round(widthDiff / 2); + heightExt = Math.round(heightDiff / 2); + } + this.context.drawImage( + this.sketchCanvas, + position.x - widthExt * scale, + position.y - heightExt * scale, + (this.canvas.width + 2 * widthExt) * scale, + (this.canvas.height + 2 * heightExt) * scale, + -widthExt, + -heightExt, + this.canvas.width + 2 * widthExt, + this.canvas.height + 2 * heightExt + ); + } this.context.restore(); }, diff --git a/src/rectangle.js b/src/rectangle.js index afe5da18..6223c12c 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -367,6 +367,20 @@ $.Rect.prototype = { maxY - minY); }, + /** + * Retrieves the smallest horizontal (degrees=0) rectangle which contains + * this rectangle and has integers x, y, width and height + * @returns {OpenSeadragon.Rect} + */ + getIntegerBoundingBox: function() { + var boundingBox = this.getBoundingBox(); + var x = Math.floor(boundingBox.x); + var y = Math.floor(boundingBox.y); + var width = Math.ceil(boundingBox.width + boundingBox.x - x); + var height = Math.ceil(boundingBox.height + boundingBox.y - y); + return new $.Rect(x, y, width, height); + }, + /** * Provides a string representation of the rectangle which is useful for * debugging. diff --git a/src/tile.js b/src/tile.js index d9c391ce..04f3c08a 100644 --- a/src/tile.js +++ b/src/tile.js @@ -195,7 +195,7 @@ $.Tile.prototype = { // private _hasTransparencyChannel: function() { - return this.context2D || this.url.match('.png'); + return !!this.context2D || this.url.match('.png'); }, /** diff --git a/src/tiledimage.js b/src/tiledimage.js index 9d4db782..67c2a7f3 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1448,8 +1448,15 @@ function drawTiles( tiledImage, lastDrawn ) { tiledImage._drawer.getCanvasSize(true)); } - if ( useSketch ) { - tiledImage._drawer._clear( true ); + var bounds; + if (useSketch) { + if (!sketchScale) { + // Except when edge smoothing, we only clean the part of the + // sketch canvas we are going to use for performance reasons. + bounds = tiledImage.viewport.viewportToViewerElementRectangle( + tiledImage.getClippedBounds(true)).getIntegerBoundingBox(); + } + tiledImage._drawer._clear(true, bounds); } // When scaling, we must rotate only when blending the sketch canvas to avoid @@ -1532,7 +1539,13 @@ function drawTiles( tiledImage, lastDrawn ) { if (offsetForRotation) { tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, false); } - tiledImage._drawer.blendSketch(tiledImage.opacity, sketchScale, sketchTranslate, tiledImage.compositeOperation); + tiledImage._drawer.blendSketch({ + opacity: tiledImage.opacity, + scale: sketchScale, + translate: sketchTranslate, + compositeOperation: tiledImage.compositeOperation, + bounds: bounds + }); if (offsetForRotation) { tiledImage._drawer._restoreRotationChanges(false); } From cac7052bf850b185bd4e2e49a2c75c6592f228d4 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 28 Apr 2016 11:26:09 -0400 Subject: [PATCH 58/78] Take pixelDensityRatio into account. --- src/tiledimage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 67c2a7f3..ed5f9443 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1454,7 +1454,9 @@ function drawTiles( tiledImage, lastDrawn ) { // Except when edge smoothing, we only clean the part of the // sketch canvas we are going to use for performance reasons. bounds = tiledImage.viewport.viewportToViewerElementRectangle( - tiledImage.getClippedBounds(true)).getIntegerBoundingBox(); + tiledImage.getClippedBounds(true)) + .getIntegerBoundingBox() + .times($.pixelDensityRatio); } tiledImage._drawer._clear(true, bounds); } From 1d02ba7853e8f272872d39ea0b6b1365924bf7be Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 29 Apr 2016 10:11:15 -0700 Subject: [PATCH 59/78] Changelog for #927 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 8f72af41..07ee8bb2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -40,6 +40,7 @@ OPENSEADRAGON CHANGELOG * Added full rotation support to overlays (#729, #193) * Viewport.goHome() now takes clipping into account (#910) * Improved zoom to point (#923) +* Optimized sketch canvas clearing and blending for images with opacity or transfer modes (#927) 2.1.0: From 521e020b9a1e927ab4e62d9a4fc46bb9f7789df3 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 4 May 2016 22:26:33 -0400 Subject: [PATCH 60/78] Viewport getBounds and fitBounds methods now take rotation into account. Fix #924 --- src/navigator.js | 4 +- src/tiledimage.js | 13 +--- src/viewer.js | 10 +-- src/viewport.js | 131 +++++++++++++++++++++++++-------------- test/modules/viewport.js | 79 +++++++++++++++++++++-- 5 files changed, 169 insertions(+), 68 deletions(-) diff --git a/src/navigator.js b/src/navigator.js index 4b3a98d2..e4b1a6c0 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -306,8 +306,8 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* this.updateSize(); } - if( viewport && this.viewport ) { - bounds = viewport.getBounds( true ); + if (viewport && this.viewport) { + bounds = viewport.getBoundsNoRotate(true); topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false); bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false) .minus( this.totalBorderWidths ); diff --git a/src/tiledimage.js b/src/tiledimage.js index 9d4db782..8d4b64b1 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -795,6 +795,7 @@ function updateViewport( tiledImage ) { levelOpacity, levelVisibility; + viewportBounds = viewportBounds.getBoundingBox(); viewportBounds.x -= tiledImage._xSpring.current.value; viewportBounds.y -= tiledImage._ySpring.current.value; @@ -804,18 +805,6 @@ function updateViewport( tiledImage ) { tile.beingDrawn = false; } - //Change bounds for rotation - if (degrees === 90 || degrees === 270) { - viewportBounds = viewportBounds.rotate( degrees ); - } else if (degrees !== 0 && degrees !== 180) { - // This is just an approximation. - var orthBounds = viewportBounds.rotate(90); - viewportBounds.x -= orthBounds.width / 2; - viewportBounds.y -= orthBounds.height / 2; - viewportBounds.width += orthBounds.width; - viewportBounds.height += orthBounds.height; - } - var viewportTL = viewportBounds.getTopLeft(); var viewportBR = viewportBounds.getBottomRight(); diff --git a/src/viewer.js b/src/viewer.js index da4b51ee..c9f6a98b 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2981,11 +2981,13 @@ function updateOnce( viewer ) { if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) { if ( viewer.preserveImageSizeOnResize ) { var prevContainerSize = THIS[ viewer.hash ].prevContainerSize; - var bounds = viewer.viewport.getBounds(true); + var bounds = viewer.viewport.getBoundsNoRotate(true); var deltaX = (containerSize.x - prevContainerSize.x); var deltaY = (containerSize.y - prevContainerSize.y); - var viewportDiff = viewer.viewport.deltaPointsFromPixels(new OpenSeadragon.Point(deltaX, deltaY), true); - viewer.viewport.resize(new OpenSeadragon.Point(containerSize.x, containerSize.y), false); + var viewportDiff = viewer.viewport.deltaPointsFromPixels( + new $.Point(deltaX, deltaY), true); + viewer.viewport.resize( + new $.Point(containerSize.x, containerSize.y), false); // Keep the center of the image in the center and just adjust the amount of image shown bounds.width += viewportDiff.x; @@ -2996,7 +2998,7 @@ function updateOnce( viewer ) { } else { // maintain image position - var oldBounds = viewer.viewport.getBounds(); + var oldBounds = viewer.viewport.getBoundsNoRotate(); var oldCenter = viewer.viewport.getCenter(); resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter); } diff --git a/src/viewport.js b/src/viewport.js index ad65f573..8a2ba22d 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -237,6 +237,17 @@ $.Viewport.prototype = { * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates. */ getHomeBounds: function() { + return this.getHomeBoundsNoRotate().rotate(-this.getRotation()); + }, + + /** + * Returns the home bounds in viewport coordinates. + * This method ignores the viewport rotation. Use + * {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account. + * @function + * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates. + */ + getHomeBoundsNoRotate: function() { var center = this._contentBounds.getCenter(); var width = 1.0 / this.getHomeZoom(); var height = width / this.getAspectRatio(); @@ -254,8 +265,8 @@ $.Viewport.prototype = { * @param {Boolean} immediately * @fires OpenSeadragon.Viewer.event:home */ - goHome: function( immediately ) { - if( this.viewer ){ + goHome: function(immediately) { + if (this.viewer) { /** * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). * @@ -266,11 +277,11 @@ $.Viewport.prototype = { * @property {Boolean} immediately * @property {?Object} userData - Arbitrary subscriber-defined object. */ - this.viewer.raiseEvent( 'home', { + this.viewer.raiseEvent('home', { immediately: immediately }); } - return this.fitBounds( this.getHomeBounds(), immediately ); + return this.fitBounds(this.getHomeBounds(), immediately); }, /** @@ -352,14 +363,26 @@ $.Viewport.prototype = { * @param {Boolean} current - Pass true for the current location; defaults to false (target location). * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates. */ - getBounds: function( current ) { - var center = this.getCenter( current ), - width = 1.0 / this.getZoom( current ), - height = width / this.getAspectRatio(); + getBounds: function(current) { + return this.getBoundsNoRotate(current).rotate(-this.getRotation()); + }, + + /** + * Returns the bounds of the visible area in viewport coordinates. + * This method ignores the viewport rotation. Use + * {@link OpenSeadragon.Viewport#getBounds} to take it into account. + * @function + * @param {Boolean} current - Pass true for the current location; defaults to false (target location). + * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates. + */ + getBoundsNoRotate: function(current) { + var center = this.getCenter(current); + var width = 1.0 / this.getZoom(current); + var height = width / this.getAspectRatio(); return new $.Rect( - center.x - ( width / 2.0 ), - center.y - ( height / 2.0 ), + center.x - (width / 2.0), + center.y - (height / 2.0), width, height ); @@ -371,8 +394,19 @@ $.Viewport.prototype = { * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, * including the space taken by margins, in viewport coordinates. */ - getBoundsWithMargins: function( current ) { - var bounds = this.getBounds(current); + getBoundsWithMargins: function(current) { + return this.getBoundsNoRotateWithMargins(current).rotate( + -this.getRotation(), this.getCenter(current)); + }, + + /** + * @function + * @param {Boolean} current - Pass true for the current location; defaults to false (target location). + * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, + * including the space taken by margins, in viewport coordinates. + */ + getBoundsNoRotateWithMargins: function(current) { + var bounds = this.getBoundsNoRotate(current); var factor = this._containerInnerSize.x * this.getZoom(current); bounds.x -= this._margins.left / factor; bounds.y -= this._margins.top / factor; @@ -538,7 +572,7 @@ $.Viewport.prototype = { this.zoomTo( constrainedZoom, this.zoomPoint, immediately ); } - bounds = this.getBounds(); + bounds = this.getBoundsNoRotate(); constrainedBounds = this._applyBoundaryConstraints( bounds, immediately ); @@ -564,39 +598,38 @@ $.Viewport.prototype = { * @param {Object} options (immediately=false, constraints=false) * @return {OpenSeadragon.Viewport} Chainable. */ - _fitBounds: function( bounds, options ) { + _fitBounds: function(bounds, options) { options = options || {}; var immediately = options.immediately || false; var constraints = options.constraints || false; var aspect = this.getAspectRatio(); var center = bounds.getCenter(); + + // Compute width and height of bounding box. var newBounds = new $.Rect( bounds.x, bounds.y, bounds.width, - bounds.height - ); + bounds.height, + bounds.degrees + this.getRotation()) + .getBoundingBox(); - if ( newBounds.getAspectRatio() >= aspect ) { - newBounds.height = bounds.width / aspect; - newBounds.y = center.y - newBounds.height / 2; + if (newBounds.getAspectRatio() >= aspect) { + newBounds.height = newBounds.width / aspect; } else { - newBounds.width = bounds.height * aspect; - newBounds.x = center.x - newBounds.width / 2; + newBounds.width = newBounds.height * aspect; } - this.panTo( this.getCenter( true ), true ); - this.zoomTo( this.getZoom( true ), null, true ); - - var oldBounds = this.getBounds(); - var oldZoom = this.getZoom(); - var newZoom = 1.0 / newBounds.width; + // Compute x and y from width, height and center position + newBounds.x = center.x - newBounds.width / 2; + newBounds.y = center.y - newBounds.height / 2; + var newZoom = 1.0 / newBounds.width; if (constraints) { var newBoundsAspectRatio = newBounds.getAspectRatio(); var newConstrainedZoom = Math.max( - Math.min(newZoom, this.getMaxZoom() ), + Math.min(newZoom, this.getMaxZoom()), this.getMinZoom() ); @@ -608,32 +641,32 @@ $.Viewport.prototype = { newBounds.y = center.y - newBounds.height / 2; } - newBounds = this._applyBoundaryConstraints( newBounds, immediately ); + newBounds = this._applyBoundaryConstraints(newBounds, immediately); center = newBounds.getCenter(); } if (immediately) { - this.panTo( center, true ); + this.panTo(center, true); return this.zoomTo(newZoom, null, true); } + this.panTo(this.getCenter(true), true); + this.zoomTo(this.getZoom(true), null, true); + + var oldBounds = this.getBounds(); + var oldZoom = this.getZoom(); + if (Math.abs(newZoom - oldZoom) < 0.00000001 || Math.abs(newBounds.width - oldBounds.width) < 0.00000001) { - return this.panTo( center, immediately ); + return this.panTo(center, immediately); } - var referencePoint = oldBounds.getTopLeft().times( - this._containerInnerSize.x / oldBounds.width - ).minus( - newBounds.getTopLeft().times( - this._containerInnerSize.x / newBounds.width - ) - ).divide( - this._containerInnerSize.x / oldBounds.width - - this._containerInnerSize.x / newBounds.width - ); + newBounds = newBounds.rotate(-this.getRotation()); + var referencePoint = newBounds.getTopLeft().divide(newBounds.width) + .minus(oldBounds.getTopLeft().divide(oldBounds.width)) + .divide(1 / newBounds.width - 1 / oldBounds.width); - return this.zoomTo( newZoom, referencePoint, immediately ); + return this.zoomTo(newZoom, referencePoint, immediately); }, /** @@ -754,7 +787,12 @@ $.Viewport.prototype = { }, /** + * Zooms to the specified zoom level * @function + * @param {Number} zoom The zoom level to zoom to. + * @param {OpenSeadragon.Point} [refPoint] The point which will stay at + * the same screen location. Defaults to the viewport center. + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. * @fires OpenSeadragon.Viewer.event:zoom */ @@ -844,7 +882,7 @@ $.Viewport.prototype = { * @fires OpenSeadragon.Viewer.event:resize */ resize: function( newContainerSize, maintain ) { - var oldBounds = this.getBounds(), + var oldBounds = this.getBoundsNoRotate(), newBounds = oldBounds, widthDeltaFactor; @@ -996,7 +1034,8 @@ $.Viewport.prototype = { * @returns {OpenSeadragon.Point} */ pixelFromPointNoRotate: function(point, current) { - return this._pixelFromPointNoRotate(point, this.getBounds(current)); + return this._pixelFromPointNoRotate( + point, this.getBoundsNoRotate(current)); }, /** @@ -1007,7 +1046,7 @@ $.Viewport.prototype = { * @returns {OpenSeadragon.Point} */ pixelFromPoint: function(point, current) { - return this._pixelFromPoint(point, this.getBounds(current)); + return this._pixelFromPoint(point, this.getBoundsNoRotate(current)); }, // private @@ -1038,7 +1077,7 @@ $.Viewport.prototype = { * @returns {OpenSeadragon.Point} */ pointFromPixelNoRotate: function(pixel, current) { - var bounds = this.getBounds( current ); + var bounds = this.getBoundsNoRotate(current); return pixel.minus( new $.Point(this._margins.left, this._margins.top) ).divide( diff --git a/test/modules/viewport.js b/test/modules/viewport.js index e40f82be..0b06b93c 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -5,6 +5,7 @@ var VIEWER_ID = "example"; var PREFIX_URL = "/build/openseadragon/images/"; var SPRING_STIFFNESS = 100; // Faster animation = faster tests + var EPSILON = 0.0000000001; module("viewport", { setup: function () { @@ -218,7 +219,27 @@ }); }); - asyncTest('getHomeBoundsWithRotation', function() { + asyncTest('getHomeBoundsNoRotate with rotation', function() { + function openHandler() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + viewport.setRotation(-675); + Util.assertRectangleEquals( + viewport.getHomeBoundsNoRotate(), + new OpenSeadragon.Rect( + (1 - Math.sqrt(2)) / 2, + (1 - Math.sqrt(2)) / 2, + Math.sqrt(2), + Math.sqrt(2)), + 0.00000001, + "Test getHomeBoundsNoRotate with degrees = -675"); + start(); + } + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + asyncTest('getHomeBounds with rotation', function() { function openHandler() { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; @@ -226,10 +247,11 @@ Util.assertRectangleEquals( viewport.getHomeBounds(), new OpenSeadragon.Rect( - (1 - Math.sqrt(2)) / 2, - (1 - Math.sqrt(2)) / 2, + 0.5, + -0.5, Math.sqrt(2), - Math.sqrt(2)), + Math.sqrt(2), + 45), 0.00000001, "Test getHomeBounds with degrees = -675"); start(); @@ -420,6 +442,33 @@ new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5) ]; + var expectedRectsFitBoundsWithRotation = [ + new OpenSeadragon.Rect( + 0.25, + -1, + Math.sqrt(0.125) + Math.sqrt(0.5), + Math.sqrt(0.125) + Math.sqrt(0.5), + 45), + new OpenSeadragon.Rect( + 0.75, + -0.25, + Math.sqrt(0.125) + Math.sqrt(8 / 25), + Math.sqrt(0.125) + Math.sqrt(8 / 25), + 45), + new OpenSeadragon.Rect( + 1, + 0.5, + Math.sqrt(0.125) * 2, + Math.sqrt(0.125) * 2, + 45), + new OpenSeadragon.Rect( + -0.05, + -0.55, + Math.sqrt(0.125) * 2, + Math.sqrt(0.125) * 2, + 45) + ]; + var expectedRectsFitBoundsWithConstraints = [ new OpenSeadragon.Rect(-0.25, -0.5, 1, 1), new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8), @@ -447,6 +496,28 @@ viewer.open(DZI_PATH); }); + asyncTest('fitBounds with viewport rotation', function(){ + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + viewport.setRotation(45); + + for(var i = 0; i < testRectsFitBounds.length; i++){ + var rect = testRectsFitBounds[i]; + viewport.fitBounds(rect, true); + Util.assertRectangleEquals( + viewport.getBounds(), + expectedRectsFitBoundsWithRotation[i], + EPSILON, + "Fit bounds correctly." + ); + } + start(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + asyncTest('fitBoundsWithConstraints', function(){ var openHandler = function(event) { viewer.removeHandler('open', openHandler); From 7ea8733e5b83a7120d4b60617c6a2c45a9b147fb Mon Sep 17 00:00:00 2001 From: leesei Date: Fri, 6 May 2016 09:00:01 +0800 Subject: [PATCH 61/78] feat(navigator): add option autoFade --- src/navigator.js | 2 +- src/openseadragon.js | 13 +++++++++---- src/viewer.js | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/navigator.js b/src/navigator.js index 4b3a98d2..a8767483 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -62,7 +62,7 @@ $.Navigator = function( options ){ options.controlOptions = { anchor: $.ControlAnchor.TOP_RIGHT, attachToViewer: true, - autoFade: true + autoFade: options.autoFade }; if( options.position ){ diff --git a/src/openseadragon.js b/src/openseadragon.js index 4acf9130..e2a03b19 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -354,16 +354,16 @@ * * @property {String} [navigatorId=navigator-GENERATED DATE] * The ID of a div to hold the navigator minimap. - * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored. + * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored. * If an ID is not specified, a div element will be generated and placed on top of the main image. * * @property {String} [navigatorPosition='TOP_RIGHT'] * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.
- * If 'ABSOLUTE' is specified, then navigatorTop|Left|Height|Width determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
- * For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigatorHeight|Width values determine the size of the navigator minimap. + * If 'ABSOLUTE' is specified, then navigator[Top|Left|Height|Width] determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
+ * For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigator[Height|Width] values determine the size of the navigator minimap. * * @property {Number} [navigatorSizeRatio=0.2] - * Ratio of navigator size to viewer size. Ignored if navigatorHeight|Width are specified. + * Ratio of navigator size to viewer size. Ignored if navigator[Height|Width] are specified. * * @property {Boolean} [navigatorMaintainSizeRatio=false] * If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes. @@ -386,6 +386,10 @@ * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior. * Setting to false can also improve performance when the navigator is configured to a fixed size. * + * @property {Boolean} [navigatorAutoFade=true] + * If the user stops interacting with the viewport, fade the navigator minimap. + * Setting to false will make the navigator minimap always visible. + * * @property {Boolean} [navigatorRotate=true] * If true, the navigator will be rotated together with the viewer. * @@ -1059,6 +1063,7 @@ if (typeof define === 'function' && define.amd) { navigatorHeight: null, navigatorWidth: null, navigatorAutoResize: true, + navigatorAutoFade: true, navigatorRotate: true, // INITIAL ROTATION diff --git a/src/viewer.js b/src/viewer.js index da4b51ee..8c132570 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -417,6 +417,7 @@ $.Viewer = function( options ) { width: this.navigatorWidth, height: this.navigatorHeight, autoResize: this.navigatorAutoResize, + autoFade: this.navigatorAutoFade, prefixUrl: this.prefixUrl, viewer: this, navigatorRotate: this.navigatorRotate, From e4c29d649b5b1752c9eca86eb2051859a2722a37 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 10 May 2016 18:49:55 -0400 Subject: [PATCH 62/78] Remove code duplication in Viewport.applyConstraints. --- src/viewport.js | 63 ++++++++++++++-------------- test/modules/events.js | 90 +++++++++++++++++++++++----------------- test/modules/viewport.js | 41 +++++++++++++++++- 3 files changed, 122 insertions(+), 72 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 8a2ba22d..3c08c594 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -555,40 +555,27 @@ $.Viewport.prototype = { }, /** + * Enforces the minZoom, maxZoom and visibilityRatio constraints by + * zooming and panning to the closest acceptable zoom and location. * @function + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. * @fires OpenSeadragon.Viewer.event:constrain */ - applyConstraints: function( immediately ) { - var actualZoom = this.getZoom(), - constrainedZoom = Math.max( - Math.min( actualZoom, this.getMaxZoom() ), - this.getMinZoom() - ), - bounds, - constrainedBounds; - - if ( actualZoom != constrainedZoom ) { - this.zoomTo( constrainedZoom, this.zoomPoint, immediately ); - } - - bounds = this.getBoundsNoRotate(); - - constrainedBounds = this._applyBoundaryConstraints( bounds, immediately ); - - if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){ - this.fitBounds( constrainedBounds, immediately ); - } - + applyConstraints: function(immediately) { + this.fitBoundsWithConstraints(this.getBounds(), immediately); return this; }, /** + * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints} * @function - * @param {Boolean} immediately + * @param {Boolean} [immediately=false] + * @return {OpenSeadragon.Viewport} Chainable. + * @fires OpenSeadragon.Viewer.event:constrain */ - ensureVisible: function( immediately ) { - return this.applyConstraints( immediately ); + ensureVisible: function(immediately) { + return this.applyConstraints(immediately); }, /** @@ -670,29 +657,41 @@ $.Viewport.prototype = { }, /** + * Makes the viewport zoom and pan so that the specified bounds take + * as much space as possible in the viewport. + * Note: this method ignores the constraints (minZoom, maxZoom and + * visibilityRatio). + * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce + * them. * @function * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. */ - fitBounds: function( bounds, immediately ) { - return this._fitBounds( bounds, { + fitBounds: function(bounds, immediately) { + return this._fitBounds(bounds, { immediately: immediately, constraints: false - } ); + }); }, /** + * Makes the viewport zoom and pan so that the specified bounds take + * as much space as possible in the viewport while enforcing the constraints + * (minZoom, maxZoom and visibilityRatio). + * Note: because this method enforces the constraints, part of the + * provided bounds may end up outside of the viewport. + * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them. * @function * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. */ - fitBoundsWithConstraints: function( bounds, immediately ) { - return this._fitBounds( bounds, { + fitBoundsWithConstraints: function(bounds, immediately) { + return this._fitBounds(bounds, { immediately: immediately, constraints: true - } ); + }); }, /** diff --git a/test/modules/events.js b/test/modules/events.js index 18f50c37..2d435fc0 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -678,45 +678,20 @@ } ); // ---------- - asyncTest( 'Viewer: preventDefaultAction', function () { - var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ), - tracker = viewer.innerTracker, - origClickHandler, - origDragHandler, - dragCount = 10, - originalZoom = 0, - originalBounds = null; - - var onOpen = function ( event ) { - viewer.removeHandler( 'open', onOpen ); - - // Hook viewer events to set preventDefaultAction - origClickHandler = tracker.clickHandler; - tracker.clickHandler = function ( event ) { - event.preventDefaultAction = true; - return origClickHandler( event ); - }; - origDragHandler = tracker.dragHandler; - tracker.dragHandler = function ( event ) { - event.preventDefaultAction = true; - return origDragHandler( event ); - }; - - originalZoom = viewer.viewport.getZoom(); - originalBounds = viewer.viewport.getBounds(); - - var event = { - clientX:1, - clientY:1 - }; + asyncTest('Viewer: preventDefaultAction', function() { + var $canvas = $(viewer.element).find('.openseadragon-canvas') + .not('.navigator .openseadragon-canvas'); + var tracker = viewer.innerTracker; + var epsilon = 0.0000001; + function simulateClickAndDrag() { $canvas.simulate( 'focus', event ); // Drag to pan Util.simulateViewerClickWithDrag( { viewer: viewer, widthFactor: 0.25, heightFactor: 0.25, - dragCount: dragCount, + dragCount: 10, dragDx: 1, dragDy: 1 } ); @@ -730,20 +705,57 @@ dragDy: 0 } ); $canvas.simulate( 'blur', event ); + } - var zoom = viewer.viewport.getZoom(), - bounds = viewer.viewport.getBounds(); + var onOpen = function() { + viewer.removeHandler('open', onOpen); - equal( zoom, originalZoom, "Zoom prevented" ); - ok( bounds.x == originalBounds.x && bounds.y == originalBounds.y, 'Pan prevented' ); + // Hook viewer events to set preventDefaultAction + var origClickHandler = tracker.clickHandler; + tracker.clickHandler = function(event) { + event.preventDefaultAction = true; + return origClickHandler(event); + }; + var origDragHandler = tracker.dragHandler; + tracker.dragHandler = function(event) { + event.preventDefaultAction = true; + return origDragHandler(event); + }; + + var originalZoom = viewer.viewport.getZoom(); + var originalBounds = viewer.viewport.getBounds(); + + simulateClickAndDrag(); + + var zoom = viewer.viewport.getZoom(); + var bounds = viewer.viewport.getBounds(); + Util.assessNumericValue(zoom, originalZoom, epsilon, + "Zoom should be prevented"); + Util.assertRectangleEquals(bounds, originalBounds, epsilon, + 'Pan should be prevented'); + + tracker.clickHandler = origClickHandler; + tracker.dragHandler = origDragHandler; + + simulateClickAndDrag(); + + var zoom = viewer.viewport.getZoom(); + var bounds = viewer.viewport.getBounds(); + Util.assessNumericValue(zoom, 0.002, epsilon, + "Zoom should not be prevented"); + Util.assertRectangleEquals( + bounds, + new OpenSeadragon.Rect(-250, -0.25, 500, 0.5), + epsilon, + 'Pan should not be prevented'); viewer.close(); start(); }; - viewer.addHandler( 'open', onOpen ); - viewer.open( '/test/data/testpattern.dzi' ); - } ); + viewer.addHandler('open', onOpen); + viewer.open('/test/data/testpattern.dzi'); + }); // ---------- asyncTest( 'EventSource/MouseTracker/Viewer: event.originalEvent event.userData canvas-drag canvas-drag-end canvas-release canvas-click', function () { diff --git a/test/modules/viewport.js b/test/modules/viewport.js index 0b06b93c..8412a007 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -409,7 +409,7 @@ viewer.open(DZI_PATH); }); - asyncTest('ensureVisible', function(){ + asyncTest('ensureVisible', function() { var openHandler = function(event) { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; @@ -427,6 +427,45 @@ viewer.open(DZI_PATH); }); + asyncTest('applyConstraints', function() { + var openHandler = function() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); + viewport.visibilityRatio = 0.3; + viewport.applyConstraints(true); + var bounds = viewport.getBounds(); + Util.assertRectangleEquals( + bounds, + new OpenSeadragon.Rect(0.7, 0.7, 1, 1), + EPSILON, + "Viewport.applyConstraints should move viewport."); + start(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + asyncTest('applyConstraints with rotation', function() { + var openHandler = function() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + viewport.setRotation(45); + viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); + viewport.applyConstraints(true); + var bounds = viewport.getBounds(); + Util.assertRectangleEquals( + bounds, + new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45), + EPSILON, + "Viewport.applyConstraints with rotation should move viewport."); + start(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + // Fit bounds tests var testRectsFitBounds = [ new OpenSeadragon.Rect(0, -0.75, 0.5, 1), From 0c398eacdbb5f95116cdb56d23aae8e9f44d5f32 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 10 May 2016 21:19:33 -0400 Subject: [PATCH 63/78] Add test for fitBounds with a rotated rectangle. --- test/modules/viewport.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/modules/viewport.js b/test/modules/viewport.js index 8412a007..c3618be9 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -471,14 +471,16 @@ new OpenSeadragon.Rect(0, -0.75, 0.5, 1), new OpenSeadragon.Rect(0.5, 0, 0.5, 0.8), new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5), - new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5) + new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5), + new OpenSeadragon.Rect(0.5, 0.25, Math.sqrt(0.125), Math.sqrt(0.125), 45) ]; var expectedRectsFitBounds = [ new OpenSeadragon.Rect(-0.25, -0.75, 1, 1), new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8), new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5), - new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5) + new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5), + new OpenSeadragon.Rect(0.25, 0.25, 0.5, 0.5) ]; var expectedRectsFitBoundsWithRotation = [ @@ -505,6 +507,12 @@ -0.55, Math.sqrt(0.125) * 2, Math.sqrt(0.125) * 2, + 45), + new OpenSeadragon.Rect( + 0.5, + 0.25, + Math.sqrt(0.125), + Math.sqrt(0.125), 45) ]; @@ -512,7 +520,8 @@ new OpenSeadragon.Rect(-0.25, -0.5, 1, 1), new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8), new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5), - new OpenSeadragon.Rect(-0.25, -0.25, 0.5, 0.5) + new OpenSeadragon.Rect(-0.25, -0.25, 0.5, 0.5), + new OpenSeadragon.Rect(0.25, 0.25, 0.5, 0.5) ]; asyncTest('fitBounds', function(){ From 07d66ce655a1ac33398d359d928b6c63f86457e5 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 12 May 2016 18:01:18 -0400 Subject: [PATCH 64/78] Restore applyConstraints to avoid panning when clicking at max zoom. --- src/viewport.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 3c08c594..ba65048b 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -474,6 +474,13 @@ $.Viewport.prototype = { } }, + // private + _applyZoomConstraints: function(zoom) { + return Math.max( + Math.min(zoom, this.getMaxZoom()), + this.getMinZoom()); + }, + /** * @function * @private @@ -563,7 +570,23 @@ $.Viewport.prototype = { * @fires OpenSeadragon.Viewer.event:constrain */ applyConstraints: function(immediately) { - this.fitBoundsWithConstraints(this.getBounds(), immediately); + var actualZoom = this.getZoom(); + var constrainedZoom = this._applyZoomConstraints(actualZoom); + + if (actualZoom !== constrainedZoom) { + this.zoomTo(constrainedZoom, this.zoomPoint, immediately); + } + + var bounds = this.getBoundsNoRotate(); + var constrainedBounds = this._applyBoundaryConstraints( + bounds, immediately); + + if (bounds.x !== constrainedBounds.x || + bounds.y !== constrainedBounds.y || + immediately) { + this.fitBounds(constrainedBounds.rotate(this.getRotation()), + immediately); + } return this; }, @@ -615,10 +638,7 @@ $.Viewport.prototype = { if (constraints) { var newBoundsAspectRatio = newBounds.getAspectRatio(); - var newConstrainedZoom = Math.max( - Math.min(newZoom, this.getMaxZoom()), - this.getMinZoom() - ); + var newConstrainedZoom = this._applyZoomConstraints(newZoom); if (newZoom !== newConstrainedZoom) { newZoom = newConstrainedZoom; From 14069a64e1ab20bc6dcf92bb25a50cb1dbec55fb Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 12 May 2016 18:47:35 -0400 Subject: [PATCH 65/78] Fix applyConstraints with rotation. --- src/viewport.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/viewport.js b/src/viewport.js index ba65048b..e5c4f51a 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -584,7 +584,8 @@ $.Viewport.prototype = { if (bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately) { - this.fitBounds(constrainedBounds.rotate(this.getRotation()), + this.fitBounds( + constrainedBounds.rotate(-this.getRotation()), immediately); } return this; From 1014d5767cdc50b080ec4d5c3271874cd9c95ff2 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 12 May 2016 19:23:09 -0400 Subject: [PATCH 66/78] Fix resize handling. --- src/viewer.js | 66 ++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index c9f6a98b..5fdb1c58 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2975,35 +2975,26 @@ function updateOnce( viewer ) { return; } - var containerSize; - if ( viewer.autoResize ) { - containerSize = _getSafeElemSize( viewer.container ); - if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) { - if ( viewer.preserveImageSizeOnResize ) { - var prevContainerSize = THIS[ viewer.hash ].prevContainerSize; - var bounds = viewer.viewport.getBoundsNoRotate(true); - var deltaX = (containerSize.x - prevContainerSize.x); - var deltaY = (containerSize.y - prevContainerSize.y); - var viewportDiff = viewer.viewport.deltaPointsFromPixels( - new $.Point(deltaX, deltaY), true); - viewer.viewport.resize( - new $.Point(containerSize.x, containerSize.y), false); - - // Keep the center of the image in the center and just adjust the amount of image shown - bounds.width += viewportDiff.x; - bounds.height += viewportDiff.y; - bounds.x -= (viewportDiff.x / 2); - bounds.y -= (viewportDiff.y / 2); - viewer.viewport.fitBoundsWithConstraints(bounds, true); - } - else { + if (viewer.autoResize) { + var containerSize = _getSafeElemSize(viewer.container); + var prevContainerSize = THIS[viewer.hash].prevContainerSize; + if (!containerSize.equals(prevContainerSize)) { + var viewport = viewer.viewport; + if (viewer.preserveImageSizeOnResize) { + var resizeRatio = prevContainerSize.x / containerSize.x; + var zoom = viewport.getZoom() * resizeRatio; + var center = viewport.getCenter(); + viewport.resize(containerSize, false); + viewport.zoomTo(zoom, null, true); + viewport.panTo(center, true); + } else { // maintain image position - var oldBounds = viewer.viewport.getBoundsNoRotate(); - var oldCenter = viewer.viewport.getCenter(); - resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter); + var oldBounds = viewport.getBounds(); + viewport.resize(containerSize, true); + viewport.fitBoundsWithConstraints(oldBounds, true); } - THIS[ viewer.hash ].prevContainerSize = containerSize; - THIS[ viewer.hash ].forceRedraw = true; + THIS[viewer.hash].prevContainerSize = containerSize; + THIS[viewer.hash].forceRedraw = true; } } @@ -3088,27 +3079,6 @@ function updateOnce( viewer ) { //viewer.profiler.endUpdate(); } -// This function resizes the viewport and recenters the image -// as it was before resizing. -// TODO: better adjust width and height. The new width and height -// should depend on the image dimensions and on the dimensions -// of the viewport before and after switching mode. -function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter ) { - var viewport = viewer.viewport; - - viewport.resize( containerSize, true ); - - var newBounds = new $.Rect( - oldCenter.x - ( oldBounds.width / 2.0 ), - oldCenter.y - ( oldBounds.height / 2.0 ), - oldBounds.width, - oldBounds.height - ); - - // let the viewport decide if the bounds are too big or too small - viewport.fitBoundsWithConstraints( newBounds, true ); -} - function drawWorld( viewer ) { viewer.imageLoader.clear(); viewer.drawer.clear(); From 6099962e408a8e62a068b2c23fd85e69a88e3f40 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 13 May 2016 11:24:15 -0700 Subject: [PATCH 67/78] Changelog for #934 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 07ee8bb2..736ed3e4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -41,6 +41,7 @@ OPENSEADRAGON CHANGELOG * Viewport.goHome() now takes clipping into account (#910) * Improved zoom to point (#923) * Optimized sketch canvas clearing and blending for images with opacity or transfer modes (#927) +* Now taking rotation into account in viewport getBounds and fitBounds methods (#934) 2.1.0: From 352bfbc3a513476b4f12696f29c473d754fb4ccb Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 13 May 2016 15:18:37 -0400 Subject: [PATCH 68/78] Avoid loading clipped out tiles. Fix #889. --- src/rectangle.js | 163 +++++++++++++++++++++++++++++++++++++- src/tiledimage.js | 19 +++-- test/modules/rectangle.js | 82 +++++++++++++++++++ 3 files changed, 255 insertions(+), 9 deletions(-) diff --git a/src/rectangle.js b/src/rectangle.js index 6223c12c..3f04afc6 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -307,6 +307,132 @@ $.Rect.prototype = { bottom - top); }, + /** + * Returns the bounding box of the intersection of this rectangle with the + * given rectangle. + * @param {OpenSeadragon.Rect} rect + * @return {OpenSeadragon.Rect} the bounding box of the intersection + * or null if the rectangles don't intersect. + */ + intersection: function(rect) { + // Simplified version of Weiler Atherton clipping algorithm + // https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm + // Because we just want the bounding box of the intersection, + // we can just compute the bounding box of: + // 1. all the summits of this which are inside rect + // 2. all the summits of rect which are inside this + // 3. all the intersections of rect and this + var EPSILON = 0.0000000001; + + var intersectionPoints = []; + + var thisTopLeft = this.getTopLeft(); + if (rect.containsPoint(thisTopLeft, EPSILON)) { + intersectionPoints.push(thisTopLeft); + } + var thisTopRight = this.getTopRight(); + if (rect.containsPoint(thisTopRight, EPSILON)) { + intersectionPoints.push(thisTopRight); + } + var thisBottomLeft = this.getBottomLeft(); + if (rect.containsPoint(thisBottomLeft, EPSILON)) { + intersectionPoints.push(thisBottomLeft); + } + var thisBottomRight = this.getBottomRight(); + if (rect.containsPoint(thisBottomRight, EPSILON)) { + intersectionPoints.push(thisBottomRight); + } + + var rectTopLeft = rect.getTopLeft(); + if (this.containsPoint(rectTopLeft, EPSILON)) { + intersectionPoints.push(rectTopLeft); + } + var rectTopRight = rect.getTopRight(); + if (this.containsPoint(rectTopRight, EPSILON)) { + intersectionPoints.push(rectTopRight); + } + var rectBottomLeft = rect.getBottomLeft(); + if (this.containsPoint(rectBottomLeft, EPSILON)) { + intersectionPoints.push(rectBottomLeft); + } + var rectBottomRight = rect.getBottomRight(); + if (this.containsPoint(rectBottomRight, EPSILON)) { + intersectionPoints.push(rectBottomRight); + } + + var thisSegments = this._getSegments(); + var rectSegments = rect._getSegments(); + for (var i = 0; i < thisSegments.length; i++) { + var thisSegment = thisSegments[i]; + for (var j = 0; j < rectSegments.length; j++) { + var rectSegment = rectSegments[j]; + var point = getIntersection(thisSegment[0], thisSegment[1], + rectSegment[0], rectSegment[1]); + if (point) { + intersectionPoints.push(point); + } + } + } + + // Get intersection point of segments [a,b] and [c,d] + function getIntersection(a, b, c, d) { + // http://stackoverflow.com/a/1968345/1440403 + var abVector = b.minus(a); + var cdVector = d.minus(c); + + var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y; + if (denom === 0) { + return null; + } + + var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom; + var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom; + + if (-EPSILON <= s && s <= 1 - EPSILON && + -EPSILON <= t && t <= 1 - EPSILON) { + return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y); + } + return null; + } + + if (intersectionPoints.length === 0) { + return null; + } + + var minX = intersectionPoints[0].x; + var maxX = intersectionPoints[0].x; + var minY = intersectionPoints[0].y; + var maxY = intersectionPoints[0].y; + for (var i = 1; i < intersectionPoints.length; i++) { + var point = intersectionPoints[i]; + if (point.x < minX) { + minX = point.x; + } + if (point.x > maxX) { + maxX = point.x; + } + if (point.y < minY) { + minY = point.y; + } + if (point.y > maxY) { + maxY = point.y; + } + } + return new $.Rect(minX, minY, maxX - minX, maxY - minY); + }, + + // private + _getSegments: function() { + var topLeft = this.getTopLeft(); + var topRight = this.getTopRight(); + var bottomLeft = this.getBottomLeft(); + var bottomRight = this.getBottomRight(); + return [[topLeft, topRight], + [topRight, bottomRight], + [bottomRight, bottomLeft], + [bottomLeft, topLeft]]; + }, + /** * Rotates a rectangle around a point. * @function @@ -381,6 +507,37 @@ $.Rect.prototype = { return new $.Rect(x, y, width, height); }, + /** + * Determines whether a point is inside this rectangle (edge included). + * @function + * @param {OpenSeadragon.Point} point + * @param {Number} [epsilon=0] the margin of error allowed + * @returns {Boolean} true if the point is inside this rectangle, false + * otherwise. + */ + containsPoint: function(point, epsilon) { + epsilon = epsilon || 0; + + // See http://stackoverflow.com/a/2752754/1440403 for explanation + var topLeft = this.getTopLeft(); + var topRight = this.getTopRight(); + var bottomLeft = this.getBottomLeft(); + var topDiff = topRight.minus(topLeft); + var leftDiff = bottomLeft.minus(topLeft); + + return ((point.x - topLeft.x) * topDiff.x + + (point.y - topLeft.y) * topDiff.y >= -epsilon) && + + ((point.x - topRight.x) * topDiff.x + + (point.y - topRight.y) * topDiff.y <= epsilon) && + + ((point.x - topLeft.x) * leftDiff.x + + (point.y - topLeft.y) * leftDiff.y >= -epsilon) && + + ((point.x - bottomLeft.x) * leftDiff.x + + (point.y - bottomLeft.y) * leftDiff.y <= epsilon); + }, + /** * Provides a string representation of the rectangle which is useful for * debugging. @@ -389,10 +546,10 @@ $.Rect.prototype = { */ toString: function() { return "[" + - (Math.round(this.x * 100) / 100) + "," + - (Math.round(this.y * 100) / 100) + "," + + (Math.round(this.x * 100) / 100) + ", " + + (Math.round(this.y * 100) / 100) + ", " + (Math.round(this.width * 100) / 100) + "x" + - (Math.round(this.height * 100) / 100) + "," + + (Math.round(this.height * 100) / 100) + ", " + (Math.round(this.degrees * 100) / 100) + "deg" + "]"; } diff --git a/src/tiledimage.js b/src/tiledimage.js index b2b33efa..91f76c51 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -787,7 +787,6 @@ function updateViewport( tiledImage ) { Math.log( 2 ) )) ), - degrees = tiledImage.viewport.degrees, renderPixelRatioC, renderPixelRatioT, zeroRatioT, @@ -795,16 +794,24 @@ function updateViewport( tiledImage ) { levelOpacity, levelVisibility; - viewportBounds = viewportBounds.getBoundingBox(); - viewportBounds.x -= tiledImage._xSpring.current.value; - viewportBounds.y -= tiledImage._ySpring.current.value; - // Reset tile's internal drawn state - while ( tiledImage.lastDrawn.length > 0 ) { + while (tiledImage.lastDrawn.length > 0) { tile = tiledImage.lastDrawn.pop(); tile.beingDrawn = false; } + if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) { + var tiledImageBounds = tiledImage.getClippedBounds(true); + var intersection = viewportBounds.intersection(tiledImageBounds); + if (intersection === null) { + return; + } + viewportBounds = intersection; + } + viewportBounds = viewportBounds.getBoundingBox(); + viewportBounds.x -= tiledImage._xSpring.current.value; + viewportBounds.y -= tiledImage._ySpring.current.value; + var viewportTL = viewportBounds.getTopLeft(); var viewportBR = viewportBounds.getBottomRight(); diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js index 7e905f58..402e58c4 100644 --- a/test/modules/rectangle.js +++ b/test/modules/rectangle.js @@ -161,6 +161,65 @@ "Incorrect union with non horizontal rectangles."); }); + test('intersection', function() { + var rect1 = new OpenSeadragon.Rect(2, 2, 2, 3); + var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1); + var expected = null; + var actual = rect1.intersection(rect2); + equal(expected, actual, + "Rectangle " + rect2 + " should not intersect " + rect1); + actual = rect2.intersection(rect1); + equal(expected, actual, + "Rectangle " + rect1 + " should not intersect " + rect2); + + rect1 = new OpenSeadragon.Rect(0, 0, 2, 1); + rect2 = new OpenSeadragon.Rect(1, 0, 2, 2); + expected = new OpenSeadragon.Rect(1, 0, 1, 1); + actual = rect1.intersection(rect2); + Util.assertRectangleEquals(expected, actual, precision, + "Intersection of " + rect2 + " with " + rect1 + " should be " + + expected); + actual = rect2.intersection(rect1); + Util.assertRectangleEquals(expected, actual, precision, + "Intersection of " + rect1 + " with " + rect2 + " should be " + + expected); + + rect1 = new OpenSeadragon.Rect(0, 0, 3, 3); + rect2 = new OpenSeadragon.Rect(1, 1, 1, 1); + expected = new OpenSeadragon.Rect(1, 1, 1, 1); + actual = rect1.intersection(rect2); + Util.assertRectangleEquals(expected, actual, precision, + "Intersection of " + rect2 + " with " + rect1 + " should be " + + expected); + actual = rect2.intersection(rect1); + Util.assertRectangleEquals(expected, actual, precision, + "Intersection of " + rect1 + " with " + rect2 + " should be " + + expected); + + + rect1 = new OpenSeadragon.Rect(2, 2, 2, 3, 45); + rect2 = new OpenSeadragon.Rect(0, 1, 1, 1); + expected = null; + actual = rect1.intersection(rect2); + equal(expected, actual, + "Rectangle " + rect2 + " should not intersect " + rect1); + actual = rect2.intersection(rect1); + equal(expected, actual, + "Rectangle " + rect1 + " should not intersect " + rect2); + + rect1 = new OpenSeadragon.Rect(2, 0, 2, 3, 45); + rect2 = new OpenSeadragon.Rect(0, 1, 1, 1); + expected = new OpenSeadragon.Rect(0, 1, 1, 1); + actual = rect1.intersection(rect2); + Util.assertRectangleEquals(expected, actual, precision, + "Intersection of " + rect2 + " with " + rect1 + " should be " + + expected); + actual = rect2.intersection(rect1); + Util.assertRectangleEquals(expected, actual, precision, + "Intersection of " + rect1 + " with " + rect2 + " should be " + + expected); + }); + test('rotate', function() { var rect = new OpenSeadragon.Rect(0, 0, 2, 1); @@ -218,4 +277,27 @@ "Bounding box of rect rotated 270deg."); }); + test('containsPoint', function() { + var rect = new OpenSeadragon.Rect(0, 0, 1, 1, 45); + + ok(rect.containsPoint(new OpenSeadragon.Point(0, 0)), + 'Point 0,0 should be inside ' + rect); + ok(rect.containsPoint(rect.getTopRight()), + 'Top right vertex should be inside ' + rect); + ok(rect.containsPoint(rect.getBottomRight()), + 'Bottom right vertex should be inside ' + rect); + ok(rect.containsPoint(rect.getBottomLeft()), + 'Bottom left vertex should be inside ' + rect); + ok(rect.containsPoint(rect.getCenter()), + 'Center should be inside ' + rect); + notOk(rect.containsPoint(new OpenSeadragon.Point(1, 0)), + 'Point 1,0 should not be inside ' + rect); + ok(rect.containsPoint(new OpenSeadragon.Point(0.5, 0.5)), + 'Point 0.5,0.5 should be inside ' + rect); + ok(rect.containsPoint(new OpenSeadragon.Point(0.4, 0.5)), + 'Point 0.4,0.5 should be inside ' + rect); + notOk(rect.containsPoint(new OpenSeadragon.Point(0.6, 0.5)), + 'Point 0.6,0.5 should not be inside ' + rect); + }); + })(); From b11edddf68ba999350fdf9a729b457c1fe7b75e3 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 13 May 2016 15:35:33 -0400 Subject: [PATCH 69/78] Fix jshint. --- src/rectangle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rectangle.js b/src/rectangle.js index 3f04afc6..98c839de 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -366,10 +366,10 @@ $.Rect.prototype = { var thisSegment = thisSegments[i]; for (var j = 0; j < rectSegments.length; j++) { var rectSegment = rectSegments[j]; - var point = getIntersection(thisSegment[0], thisSegment[1], + var intersect = getIntersection(thisSegment[0], thisSegment[1], rectSegment[0], rectSegment[1]); - if (point) { - intersectionPoints.push(point); + if (intersect) { + intersectionPoints.push(intersect); } } } @@ -403,8 +403,8 @@ $.Rect.prototype = { var maxX = intersectionPoints[0].x; var minY = intersectionPoints[0].y; var maxY = intersectionPoints[0].y; - for (var i = 1; i < intersectionPoints.length; i++) { - var point = intersectionPoints[i]; + for (var k = 1; k < intersectionPoints.length; k++) { + var point = intersectionPoints[k]; if (point.x < minX) { minX = point.x; } From a4dbae07545aecf978b7a4f12b1356abfd1bd4fb Mon Sep 17 00:00:00 2001 From: Daniel Zimmermann Date: Fri, 29 Apr 2016 18:30:30 +1000 Subject: [PATCH 70/78] Handle simultaneous touch events Found and tested on an iPhone 5s w/ iOS 9.2. Not sure about other devices. Fixes #877 --- src/mousetracker.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 4528df03..6b78f4d8 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1357,11 +1357,11 @@ * @private * @inner */ - function capturePointer( tracker, pointerType ) { + function capturePointer( tracker, pointerType, touchCount ) { var pointsList = tracker.getActivePointersListByType( pointerType ), eventParams; - pointsList.captureCount++; + pointsList.captureCount += (pointerType === 'touch' ? touchCount : 1); if ( pointsList.captureCount === 1 ) { if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { @@ -1400,11 +1400,11 @@ * @private * @inner */ - function releasePointer( tracker, pointerType ) { + function releasePointer( tracker, pointerType, touchCount ) { var pointsList = tracker.getActivePointersListByType( pointerType ), eventParams; - pointsList.captureCount--; + pointsList.captureCount -= (pointerType === 'touch' ? touchCount : 1); if ( pointsList.captureCount === 0 ) { if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { @@ -2074,7 +2074,7 @@ if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact $.stopEvent( event ); - capturePointer( tracker, 'touch' ); + capturePointer( tracker, 'touch', touchCount ); } $.cancelEvent( event ); @@ -2128,7 +2128,7 @@ } if ( updatePointersUp( tracker, event, gPoints, 0 ) ) { - releasePointer( tracker, 'touch' ); + releasePointer( tracker, 'touch', touchCount ); } // simulate touchleave on our tracked element From c25bf0a2398abc95fec5fdead07ceac33aa14dfc Mon Sep 17 00:00:00 2001 From: Daniel Zimmermann Date: Sat, 14 May 2016 22:16:36 +1000 Subject: [PATCH 71/78] Correctly handle touch PointerEvents `onPointerDown/Up` may call `capture/releasePointer` with `"touch"` as the pointerType, which would result in a bug as `touchCount` would be `undefined`. `capture/releasePointer` should just default to a count of `1` if not specified. This properly retains the existing behaviour for non-TouchEvent handling. --- src/mousetracker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 6b78f4d8..498a72ac 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1357,11 +1357,11 @@ * @private * @inner */ - function capturePointer( tracker, pointerType, touchCount ) { + function capturePointer( tracker, pointerType, pointerCount ) { var pointsList = tracker.getActivePointersListByType( pointerType ), eventParams; - pointsList.captureCount += (pointerType === 'touch' ? touchCount : 1); + pointsList.captureCount += (pointerCount || 1); if ( pointsList.captureCount === 1 ) { if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { @@ -1400,11 +1400,11 @@ * @private * @inner */ - function releasePointer( tracker, pointerType, touchCount ) { + function releasePointer( tracker, pointerType, pointerCount ) { var pointsList = tracker.getActivePointersListByType( pointerType ), eventParams; - pointsList.captureCount -= (pointerType === 'touch' ? touchCount : 1); + pointsList.captureCount -= (pointerCount || 1); if ( pointsList.captureCount === 0 ) { if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { From 6ab3d5b33cb9f1357f6b4ddaa3f0f810ae960b8c Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Mon, 16 May 2016 09:42:06 -0700 Subject: [PATCH 72/78] Changelog for #935 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 736ed3e4..bbcf71ff 100644 --- a/changelog.txt +++ b/changelog.txt @@ -42,6 +42,7 @@ OPENSEADRAGON CHANGELOG * Improved zoom to point (#923) * Optimized sketch canvas clearing and blending for images with opacity or transfer modes (#927) * Now taking rotation into account in viewport getBounds and fitBounds methods (#934) +* Added option to disable navigator auto-fade (#935) 2.1.0: From 32f993f862bf465fa06999478372cbb1c8d74081 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Fri, 13 May 2016 16:38:56 -0400 Subject: [PATCH 73/78] Enforce html element width and height to 100% when going full page. --- src/viewer.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/viewer.js b/src/viewer.js index 5fdb1c58..f8ad0035 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -918,9 +918,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, docStyle.padding = "0"; this.bodyWidth = bodyStyle.width; - this.bodyHeight = bodyStyle.height; + this.docWidth = docStyle.width; bodyStyle.width = "100%"; + docStyle.width = "100%"; + + this.bodyHeight = bodyStyle.height; + this.docHeight = docStyle.height; bodyStyle.height = "100%"; + docStyle.height = "100%"; //when entering full screen on the ipad it wasnt sufficient to leave //the body intact as only only the top half of the screen would @@ -981,7 +986,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, docStyle.padding = this.docPadding; bodyStyle.width = this.bodyWidth; + docStyle.width = this.docWidth; + bodyStyle.height = this.bodyHeight; + docStyle.height = this.docHeight; body.removeChild( this.element ); nodes = this.previousBody.length; From 7935ab82d4bbd7f26fcfb7acaed8fa85fa90f3d9 Mon Sep 17 00:00:00 2001 From: Daniel Zimmermann Date: Mon, 16 May 2016 06:26:18 +1000 Subject: [PATCH 74/78] Add unit tests for multi-touch --- test/coverage.html | 1 + test/helpers/touch.js | 134 +++++++++++++++++++++++++++++++++++++++++ test/modules/events.js | 78 +++++++++++++++++++++++- test/test.html | 1 + 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 test/helpers/touch.js diff --git a/test/coverage.html b/test/coverage.html index 81ffe579..b04d5fda 100644 --- a/test/coverage.html +++ b/test/coverage.html @@ -53,6 +53,7 @@ + From af21d7b4cd594a185a95c6f17ff770aada144f53 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 17 May 2016 09:54:03 -0700 Subject: [PATCH 75/78] Changelog for #940 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index bbcf71ff..e4f09cb6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -43,6 +43,7 @@ OPENSEADRAGON CHANGELOG * Optimized sketch canvas clearing and blending for images with opacity or transfer modes (#927) * Now taking rotation into account in viewport getBounds and fitBounds methods (#934) * Added option to disable navigator auto-fade (#935) +* Fixed issue with maintaining viewport position with full screen (#940) 2.1.0: From 12f9aa46b5819652b4423a7dd55a02d6cae54837 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 17 May 2016 10:46:11 -0700 Subject: [PATCH 76/78] Changelog for #930 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index e4f09cb6..f256f49d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -44,6 +44,7 @@ OPENSEADRAGON CHANGELOG * Now taking rotation into account in viewport getBounds and fitBounds methods (#934) * Added option to disable navigator auto-fade (#935) * Fixed issue with maintaining viewport position with full screen (#940) +* Fixed an issue with simultaneous touch events (#930) 2.1.0: From 8951ac3f5bfd142d0d3bc0e26a5fdef539aa7362 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 17 May 2016 14:27:28 -0400 Subject: [PATCH 77/78] Fix fitBounds with extreme zoom values. --- src/viewport.js | 10 +++++----- test/modules/viewport.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index e5c4f51a..da2ce761 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -664,15 +664,15 @@ $.Viewport.prototype = { var oldBounds = this.getBounds(); var oldZoom = this.getZoom(); - if (Math.abs(newZoom - oldZoom) < 0.00000001 || - Math.abs(newBounds.width - oldBounds.width) < 0.00000001) { + if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) { + this.zoomTo(newZoom, true); return this.panTo(center, immediately); } newBounds = newBounds.rotate(-this.getRotation()); - var referencePoint = newBounds.getTopLeft().divide(newBounds.width) - .minus(oldBounds.getTopLeft().divide(oldBounds.width)) - .divide(1 / newBounds.width - 1 / oldBounds.width); + var referencePoint = newBounds.getTopLeft().times(newZoom) + .minus(oldBounds.getTopLeft().times(oldZoom)) + .divide(newZoom - oldZoom); return this.zoomTo(newZoom, referencePoint, immediately); }, diff --git a/test/modules/viewport.js b/test/modules/viewport.js index c3618be9..f39473cf 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -587,6 +587,44 @@ viewer.open(DZI_PATH); }); + asyncTest('fitBounds with almost same zoom', function() { + var openHandler = function() { + var viewport = viewer.viewport; + var rect1 = new OpenSeadragon.Rect(0, 0, 1, 1); + viewport.fitBounds(rect1, true); + Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6, + 'Bounds should be ' + rect1); + + // Zoom and pan + var rect2 = new OpenSeadragon.Rect(1, 1, 1 + 1e-8, 1 + 1e-8); + viewport.fitBounds(rect2); + Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6, + 'Bounds should be ' + rect2); + start(); + }; + viewer.addOnceHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + asyncTest('fitBounds with big rectangle', function() { + var openHandler = function() { + var viewport = viewer.viewport; + var rect1 = new OpenSeadragon.Rect(0, 0, 1e9, 1e9); + viewport.fitBounds(rect1, true); + Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6, + 'Bounds should be ' + rect1); + + // Zoom and pan + var rect2 = new OpenSeadragon.Rect(1, 1, 2e9, 2e9); + viewport.fitBounds(rect2); + Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6, + 'Bounds should be ' + rect2); + start(); + }; + viewer.addOnceHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + asyncTest('fitHorizontally', function(){ var openHandler = function(event) { viewer.removeHandler('open', openHandler); From 5071540af42fc8b07f563a3d96ac304092a6595f Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Thu, 19 May 2016 09:50:10 -0700 Subject: [PATCH 78/78] Changelog for #939 --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index f256f49d..725bfdc0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -45,6 +45,8 @@ OPENSEADRAGON CHANGELOG * Added option to disable navigator auto-fade (#935) * Fixed issue with maintaining viewport position with full screen (#940) * Fixed an issue with simultaneous touch events (#930) +* Avoid loading clipped out tiles (#939) +* Improved precision for subtle moves with fitBounds (#939) 2.1.0: