diff --git a/changelog.txt b/changelog.txt index a6471ab9..fae18ba3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ OPENSEADRAGON CHANGELOG 2.4.0: (In Progress) * BREAKING CHANGE: Viewer's canvas-double-click event is now fired before it initiates the zoom (#1288) +* You can now flip the viewport to get a mirror image of the original (#1441) * You can now prevent canvas-double-click events from zooming on a per-event basis (#1288) * Fixed: Opacity 0 images were causing unnecessary redraws (#1319) * The "page" event is now fired after the page index has been updated (#1330) @@ -14,6 +15,7 @@ OPENSEADRAGON CHANGELOG * Now supporting square edge tiles that are padded rather than cropped (#1426) * Fixed an issue causing the simple image tileSource to sometimes show duplicate copies (#1370) * Fixed an issue causing seams to appear in semi-transparent PNG tiled images (#1470) +* Added visual customization options for the navigator (#1480) 2.3.1: diff --git a/images/flip_grouphover.png b/images/flip_grouphover.png new file mode 100644 index 00000000..24c110c8 Binary files /dev/null and b/images/flip_grouphover.png differ diff --git a/images/flip_hover.png b/images/flip_hover.png new file mode 100644 index 00000000..af97e717 Binary files /dev/null and b/images/flip_hover.png differ diff --git a/images/flip_pressed.png b/images/flip_pressed.png new file mode 100644 index 00000000..026d55ec Binary files /dev/null and b/images/flip_pressed.png differ diff --git a/images/flip_rest.png b/images/flip_rest.png new file mode 100644 index 00000000..97145821 Binary files /dev/null and b/images/flip_rest.png differ diff --git a/src/drawer.js b/src/drawer.js index c904abbb..fbf66a76 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -500,6 +500,10 @@ $.Drawer.prototype = { if ( this.viewport.degrees !== 0 ) { this._offsetForRotation({degrees: this.viewport.degrees}); + } else{ + if(this.viewer.viewport.flipped) { + this._flip(); + } } if (tiledImage.getRotation(true) % 360 !== 0) { this._offsetForRotation({ @@ -620,10 +624,28 @@ $.Drawer.prototype = { context.save(); context.translate(point.x, point.y); - context.rotate(Math.PI / 180 * options.degrees); + if(this.viewer.viewport.flipped){ + context.rotate(Math.PI / 180 * -options.degrees); + context.scale(-1, 1); + } else{ + context.rotate(Math.PI / 180 * options.degrees); + } context.translate(-point.x, -point.y); }, + // private + _flip: function(options) { + options = options || {}; + var point = options.point ? + options.point.times($.pixelDensityRatio) : + this.getCanvasCenter(); + var context = this._getContext(options.useSketch); + + context.translate(point.x, 0); + context.scale(-1, 1); + context.translate(-point.x, 0); + }, + // private _restoreRotationChanges: function(useSketch) { var context = this._getContext(useSketch); diff --git a/src/navigator.js b/src/navigator.js index 4c9848cf..abce42d4 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -110,7 +110,11 @@ $.Navigator = function( options ){ animationTime: 0, autoResize: options.autoResize, // prevent resizing the navigator from adding unwanted space around the image - minZoomImageRatio: 1.0 + minZoomImageRatio: 1.0, + background: options.background, + opacity: options.opacity, + borderColor: options.borderColor, + displayRegionColor: options.displayRegionColor }); options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio; @@ -127,10 +131,10 @@ $.Navigator = function( options ){ if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) { (function( style, borderWidth ){ style.margin = '0px'; - style.border = borderWidth + 'px solid #555'; + style.border = borderWidth + 'px solid ' + options.borderColor; style.padding = '0px'; - style.background = '#000'; - style.opacity = 0.8; + style.background = options.background; + style.opacity = options.opacity; style.overflow = 'hidden'; }( this.element.style, this.borderWidth)); } @@ -145,7 +149,7 @@ $.Navigator = function( options ){ style.left = '0px'; style.fontSize = '0px'; style.overflow = 'hidden'; - style.border = borderWidth + 'px solid #900'; + style.border = borderWidth + 'px solid ' + options.displayRegionColor; style.margin = '0px'; style.padding = '0px'; //TODO: IE doesnt like this property being set @@ -208,12 +212,14 @@ $.Navigator = function( options ){ var degrees = options.viewer.viewport ? options.viewer.viewport.getRotation() : options.viewer.degrees || 0; + rotate(degrees); options.viewer.addHandler("rotate", function (args) { rotate(args.degrees); }); } + // Remove the base class' (Viewer's) innerTracker and replace it with our own this.innerTracker.destroy(); this.innerTracker = new $.MouseTracker({ @@ -271,6 +277,22 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* } } }, + /** + /* Flip navigator element + * @param {Boolean} state - Flip state to set. + */ + setFlip: function(state) { + this.viewport.setFlip(state); + + this.setDisplayTransform(this.viewer.viewport.getFlip() ? "scale(-1,1)" : "scale(1,1)"); + return this; + }, + + setDisplayTransform: function(rule) { + setElementTransform(this.displayRegion, rule); + setElementTransform(this.canvas, rule); + setElementTransform(this.element, rule); + }, /** * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs. @@ -399,6 +421,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* } }); + /** * @private * @inner @@ -406,6 +429,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* */ function onCanvasClick( event ) { if ( event.quick && this.viewer.viewport ) { + if(this.viewer.viewport.flipped){ + event.position.x = this.viewport.getContainerSize().x - event.position.x; + } this.viewer.viewport.panTo(this.viewport.pointFromPixel(event.position)); this.viewer.viewport.applyConstraints(); } @@ -424,6 +450,11 @@ function onCanvasDrag( event ) { if( !this.panVertical ){ event.delta.y = 0; } + + if(this.viewer.viewport.flipped){ + event.delta.x = -event.delta.x; + } + this.viewer.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta @@ -487,12 +518,16 @@ function onCanvasScroll( event ) { * @param {Object} element * @param {Number} degrees */ -function _setTransformRotate (element, degrees) { - element.style.webkitTransform = "rotate(" + degrees + "deg)"; - element.style.mozTransform = "rotate(" + degrees + "deg)"; - element.style.msTransform = "rotate(" + degrees + "deg)"; - element.style.oTransform = "rotate(" + degrees + "deg)"; - element.style.transform = "rotate(" + degrees + "deg)"; +function _setTransformRotate( element, degrees ) { + setElementTransform(element, "rotate(" + degrees + "deg)"); +} + +function setElementTransform( element, rule ) { + element.style.webkitTransform = rule; + element.style.mozTransform = rule; + element.style.msTransform = rule; + element.style.oTransform = rule; + element.style.transform = rule; } }( OpenSeadragon )); diff --git a/src/openseadragon.js b/src/openseadragon.js index db2a3b7b..8f52b599 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -206,6 +206,9 @@ * @property {Number} [degrees=0] * Initial rotation. * + * @property {Boolean} [flipped=false] + * Initial flip state. + * * @property {Number} [minZoomLevel=null] * * @property {Number} [maxZoomLevel=null] @@ -416,6 +419,18 @@ * @property {Boolean} [navigatorRotate=true] * If true, the navigator will be rotated together with the viewer. * + * @property {String} [navigatorBackground='#000'] + * Specifies the background color of the navigator minimap + * + * @property {Number} [navigatorOpacity=0.8] + * Specifies the opacity of the navigator minimap. + * + * @property {String} [navigatorBorderColor='#555'] + * Specifies the border color of the navigator minimap + * + * @property {String} [navigatorDisplayRegionColor='#900'] + * Specifies the border color of the display region rectangle of the navigator minimap + * * @property {Number} [controlsFadeDelay=2000] * The number of milliseconds to wait once the user has stopped interacting * with the interface before begining to fade the controls. Assumes @@ -478,6 +493,10 @@ * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding * this setting when set to false. * + * @property {Boolean} [showFlipControl=false] + * If true then the flip controls will be displayed as part of the + * standard controls. + * * @property {Boolean} [showSequenceControl=true] * If sequenceMode is true, then provide buttons for navigating forward and * backward through the images. @@ -688,6 +707,12 @@ * @property {String} rotateright.HOVER * @property {String} rotateright.DOWN * + * @property {Object} flip - Images for the flip button. + * @property {String} flip.REST + * @property {String} flip.GROUP + * @property {String} flip.HOVER + * @property {String} flip.DOWN + * * @property {Object} previous - Images for the previous button. * @property {String} previous.REST * @property {String} previous.GROUP @@ -1120,6 +1145,7 @@ function OpenSeadragon( options ){ showHomeControl: true, //HOME showFullPageControl: true, //FULL showRotationControl: false, //ROTATION + showFlipControl: false, //FLIP controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY @@ -1137,10 +1163,17 @@ function OpenSeadragon( options ){ navigatorAutoResize: true, navigatorAutoFade: true, navigatorRotate: true, + navigatorBackground: '#000', + navigatorOpacity: 0.8, + navigatorBorderColor: '#555', + navigatorDisplayRegionColor: '#900', // INITIAL ROTATION degrees: 0, + // INITIAL FLIP STATE + flipped: false, + // APPEARANCE opacity: 1, preload: false, @@ -1209,6 +1242,12 @@ function OpenSeadragon( options ){ HOVER: 'rotateright_hover.png', DOWN: 'rotateright_pressed.png' }, + flip: { // Flip icon designed by Yaroslav Samoylov from the Noun Project and modified by Nelson Campos ncampos@criteriamarathon.com, https://thenounproject.com/term/flip/136289/ + REST: 'flip_rest.png', + GROUP: 'flip_grouphover.png', + HOVER: 'flip_hover.png', + DOWN: 'flip_pressed.png' + }, previous: { REST: 'previous_rest.png', GROUP: 'previous_grouphover.png', diff --git a/src/strings.js b/src/strings.js index 6ab94acb..7d0cbdd1 100644 --- a/src/strings.js +++ b/src/strings.js @@ -57,7 +57,8 @@ var I18N = { NextPage: "Next page", PreviousPage: "Previous page", RotateLeft: "Rotate left", - RotateRight: "Rotate right" + RotateRight: "Rotate right", + Flip: "Flip Horizontally" } }; diff --git a/src/tiledimage.js b/src/tiledimage.js index 49ebcb22..6fb68413 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -85,7 +85,11 @@ */ $.TiledImage = function( options ) { var _this = this; - + /** + * The {@link OpenSeadragon.TileSource} that defines this TiledImage. + * @member {OpenSeadragon.TileSource} source + * @memberof OpenSeadragon.TiledImage# + */ $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" ); $.console.assert( options.drawer, "[TiledImage] options.drawer is required" ); $.console.assert( options.viewer, "[TiledImage] options.viewer is required" ); @@ -1886,6 +1890,10 @@ function drawTiles( tiledImage, lastDrawn ) { degrees: tiledImage.viewport.degrees, useSketch: useSketch }); + } else { + if(tiledImage._drawer.viewer.viewport.flipped) { + tiledImage._drawer._flip({}); + } } if (tiledImage.getRotation(true) % 360 !== 0) { tiledImage._drawer._offsetForRotation({ @@ -1969,6 +1977,10 @@ function drawTiles( tiledImage, lastDrawn ) { } if (tiledImage.viewport.degrees !== 0) { tiledImage._drawer._restoreRotationChanges(useSketch); + } else{ + if(tiledImage._drawer.viewer.viewport.flipped) { + tiledImage._drawer._flip({}); + } } } diff --git a/src/viewer.js b/src/viewer.js index 74a2b1bb..c9bc2c99 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -145,6 +145,8 @@ $.Viewer = function( options ) { //These are originally not part options but declared as members //in initialize. It's still considered idiomatic to put them here + //source is here for backwards compatibility. It is not an official + //part of the API and should not be relied upon. source: null, /** * Handles rendering of tiles in the viewer. Created for each TileSource opened. @@ -152,6 +154,11 @@ $.Viewer = function( options ) { * @memberof OpenSeadragon.Viewer# */ drawer: null, + /** + * Keeps track of all of the tiled images in the scene. + * @member {OpenSeadragon.Drawer} world + * @memberof OpenSeadragon.Viewer# + */ world: null, /** * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened. @@ -367,6 +374,7 @@ $.Viewer = function( options ) { maxZoomLevel: this.maxZoomLevel, viewer: this, degrees: this.degrees, + flipped: this.flipped, navigatorRotate: this.navigatorRotate, homeFillsViewer: this.homeFillsViewer, margins: this.viewportMargins @@ -428,6 +436,10 @@ $.Viewer = function( options ) { prefixUrl: this.prefixUrl, viewer: this, navigatorRotate: this.navigatorRotate, + background: this.navigatorBackground, + opacity: this.navigatorOpacity, + borderColor: this.navigatorBorderColor, + displayRegionColor: this.navigatorDisplayRegionColor, crossOriginPolicy: this.crossOriginPolicy }); } @@ -1674,6 +1686,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, onFullScreenHandler = $.delegate( this, onFullScreen ), onRotateLeftHandler = $.delegate( this, onRotateLeft ), onRotateRightHandler = $.delegate( this, onRotateRight ), + onFlipHandler = $.delegate( this, onFlip), onFocusHandler = $.delegate( this, onFocus ), onBlurHandler = $.delegate( this, onBlur ), navImages = this.navImages, @@ -1685,7 +1698,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, if( this.zoomInButton || this.zoomOutButton || this.homeButton || this.fullPageButton || - this.rotateLeftButton || this.rotateRightButton ) { + this.rotateLeftButton || this.rotateRightButton || + this.flipButton ) { //if we are binding to custom buttons then layout and //grouping is the responsibility of the page author useGroup = false; @@ -1789,7 +1803,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, onFocus: onFocusHandler, onBlur: onBlurHandler })); + } + if ( this.showFlipControl ) { + buttons.push( this.flipButton = new $.Button({ + element: this.flipButton ? $.getElement( this.flipButton ) : null, + clickTimeThreshold: this.clickTimeThreshold, + clickDistThreshold: this.clickDistThreshold, + tooltip: $.getString( "Tooltips.Flip" ), + srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ), + srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ), + srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ), + srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ), + onRelease: onFlipHandler, + onFocus: onFocusHandler, + onBlur: onBlurHandler + })); } if ( useGroup ) { @@ -2602,8 +2631,27 @@ function onCanvasKeyPress( event ) { this.viewport.applyConstraints(); } return false; + case 114: //r - 90 degrees clockwise rotation + if(this.viewport.flipped){ + this.viewport.setRotation(this.viewport.degrees - 90); + } else{ + this.viewport.setRotation(this.viewport.degrees + 90); + } + this.viewport.applyConstraints(); + return false; + case 82: //R - 90 degrees counterclockwise rotation + if(this.viewport.flipped){ + this.viewport.setRotation(this.viewport.degrees + 90); + } else{ + this.viewport.setRotation(this.viewport.degrees - 90); + } + this.viewport.applyConstraints(); + return false; + case 102: //f + this.viewport.toggleFlip(); + return false; default: - //console.log( 'navigator keycode %s', event.keyCode ); + // console.log( 'navigator keycode %s', event.keyCode ); return true; } } else { @@ -2620,6 +2668,9 @@ function onCanvasClick( event ) { if ( !haveKeyboardFocus ) { this.canvas.focus(); } + if(this.viewport.flipped){ + event.position.x = this.viewport.getContainerSize().x - event.position.x; + } var canvasClickEventArgs = { tracker: event.eventSource, @@ -2739,6 +2790,9 @@ function onCanvasDrag( event ) { if( !this.panVertical ){ event.delta.y = 0; } + if(this.viewport.flipped){ + event.delta.x = -event.delta.x; + } if( this.constrainDuringPan ){ var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() ); @@ -3064,6 +3118,10 @@ function onCanvasScroll( event ) { if (deltaScrollTime > this.minScrollDeltaTime) { this._lastScrollTime = thisScrollTime; + if(this.viewport.flipped){ + event.position.x = this.viewport.getContainerSize().x - event.position.x; + } + if ( !event.preventDefaultAction && this.viewport ) { gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); if ( gestureSettings.scrollToZoom ) { @@ -3259,7 +3317,7 @@ function updateOnce( viewer ) { drawWorld( viewer ); viewer._drawOverlays(); if( viewer.navigator ){ - viewer.navigator.update( viewer.viewport ); + viewer.navigator.update( viewer.viewport ); } THIS[ viewer.hash ].forceRedraw = false; @@ -3429,11 +3487,11 @@ function onFullScreen() { function onRotateLeft() { if ( this.viewport ) { var currRotation = this.viewport.getRotation(); - if (currRotation === 0) { - currRotation = 270; - } - else { - currRotation -= 90; + + if ( this.viewport.flipped ){ + currRotation = $.positiveModulo(currRotation + 90, 360); + } else { + currRotation = $.positiveModulo(currRotation - 90, 360); } this.viewport.setRotation(currRotation); } @@ -3445,16 +3503,22 @@ function onRotateLeft() { function onRotateRight() { if ( this.viewport ) { var currRotation = this.viewport.getRotation(); - if (currRotation === 270) { - currRotation = 0; - } - else { - currRotation += 90; + + if ( this.viewport.flipped ){ + currRotation = $.positiveModulo(currRotation - 90, 360); + } else { + currRotation = $.positiveModulo(currRotation + 90, 360); } this.viewport.setRotation(currRotation); } } +/** + * Note: When pressed flip control button + */ +function onFlip() { + this.viewport.toggleFlip(); +} function onPrevious(){ var previous = this._sequenceIndex - 1; diff --git a/src/viewport.js b/src/viewport.js index 9d357910..dec8513f 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -107,6 +107,7 @@ $.Viewport = function( options ) { minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel, maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel, degrees: $.DEFAULT_SETTINGS.degrees, + flipped: $.DEFAULT_SETTINGS.flipped, homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer }, options ); @@ -880,7 +881,6 @@ $.Viewport.prototype = { if (!this.viewer || !this.viewer.drawer.canRotate()) { return this; } - this.degrees = $.positiveModulo(degrees, 360); this._setContentBounds( this.viewer.world.getHomeBounds(), @@ -1518,7 +1518,58 @@ $.Viewport.prototype = { var scale = this._contentBoundsNoRotate.width; var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale; return imageZoom * viewportToImageZoomRatio; + }, + + /** + * Toggles flip state and demands a new drawing on navigator and viewer objects. + * @function + * @return {OpenSeadragon.Viewport} Chainable. + */ + toggleFlip: function() { + this.setFlip(!this.getFlip()); + return this; + }, + + /** + * Gets flip state stored on viewport. + * @function + * @return {Boolean} Flip state. + */ + getFlip: function() { + return this.flipped; + }, + + /** + * Sets flip state according to the state input argument. + * @function + * @param {Boolean} state - Flip state to set. + * @return {OpenSeadragon.Viewport} Chainable. + */ + setFlip: function( state ) { + if ( this.flipped === state ) { + return this; + } + + this.flipped = state; + if(this.viewer.navigator){ + this.viewer.navigator.setFlip(this.getFlip()); + } + this.viewer.forceRedraw(); + + /** + * Raised when flip state has been changed. + * + * @event flip + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {Number} flipped - The flip state after this change. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.viewer.raiseEvent('flip', {"flipped": state}); + return this; } + }; }( OpenSeadragon )); diff --git a/test/modules/viewport.js b/test/modules/viewport.js index bebe7d77..d820d5d6 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -118,6 +118,7 @@ assert, actual, expected, + 1e-15, "Correctly converted coordinates " + orig ); } else { @@ -136,8 +137,8 @@ viewer.open(DZI_PATH); }; - function assertPointsEquals(assert, actual, expected, message) { - Util.assertPointsEquals(assert, actual, expected, 1e-15, message); + function assertPointsEquals(assert, actual, expected, variance, message) { + Util.assertPointsEquals(assert, actual, expected, variance, message); } // Tests start here. @@ -470,6 +471,32 @@ bounds, EPSILON, "Viewport.applyConstraints should move viewport."); + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + QUnit.test('applyConstraints flipped', function(assert) { + var done = assert.async(); + var openHandler = function() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + + viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); + viewport.visibilityRatio = 0.3; + viewport.applyConstraints(true); + var bounds = viewport.getBounds(); + Util.assertRectangleEquals( + assert, + new OpenSeadragon.Rect(0.7, 0.7, 1, 1), + bounds, + EPSILON, + "Viewport.applyConstraints should move flipped viewport."); + done(); }; viewer.addHandler('open', openHandler); @@ -514,6 +541,32 @@ new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45), EPSILON, "Viewport.applyConstraints with rotation should move viewport."); + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + QUnit.test('applyConstraints flipped with rotation', function(assert) { + var done = assert.async(); + var openHandler = function() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + viewport.setRotation(45); + + viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); + viewport.applyConstraints(true); + var bounds = viewport.getBounds(); + Util.assertRectangleEquals( + assert, + bounds, + new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45), + EPSILON, + "Viewport.applyConstraints flipped and with rotation should move viewport."); + done(); }; viewer.addHandler('open', openHandler); @@ -742,6 +795,30 @@ viewer.open(DZI_PATH); }); + QUnit.test('panBy flipped', function(assert) { + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + + for (var i = 0; i < testPoints.length; i++){ + var expected = viewport.getCenter().plus(testPoints[i]); + viewport.panBy(testPoints[i], true); + assert.propEqual( + viewport.getCenter(), + expected, + "Panned flipped by the correct amount." + ); + } + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + QUnit.test('panTo', function(assert) { var done = assert.async(); var openHandler = function(event) { @@ -763,6 +840,29 @@ viewer.open(DZI_PATH); }); + QUnit.test('panTo flipped', function(assert) { + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + + for (var i = 0; i < testPoints.length; i++){ + viewport.panTo(testPoints[i], true); + assert.propEqual( + viewport.getCenter(), + testPoints[i], + "Panned flipped to the correct location." + ); + } + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + QUnit.test('zoomBy no ref point', function(assert) { var done = assert.async(); var openHandler = function(event) { @@ -821,6 +921,45 @@ viewer.open(DZI_PATH); }); + QUnit.test('zoomBy flipped with ref point', function(assert) { + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + + var expectedFlippedCenters = [ + new OpenSeadragon.Point(5, 5), + new OpenSeadragon.Point(6.996, 6.996), + new OpenSeadragon.Point(7.246, 6.996), + new OpenSeadragon.Point(7.246, 6.996), + new OpenSeadragon.Point(7.621, 7.371), + new OpenSeadragon.Point(7.621, 7.371), + ]; + + for (var i = 0; i < testZoomLevels.length; i++) { + viewport.zoomBy(testZoomLevels[i], testPoints[i], true); + assert.propEqual( + testZoomLevels[i], + viewport.getZoom(), + "Zoomed flipped by the correct amount." + ); + assertPointsEquals( + assert, + expectedFlippedCenters[i], + viewport.getCenter(), + 1e-6, + "Panned flipped to the correct location." + ); + } + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + QUnit.test('zoomTo no ref point', function(assert) { var done = assert.async(); var openHandler = function(event) { @@ -866,8 +1005,8 @@ ); assertPointsEquals( assert, - viewport.getCenter(), expectedCenters[i], + viewport.getCenter(), 1e-14, "Panned to the correct location." ); @@ -879,6 +1018,45 @@ viewer.open(DZI_PATH); }); + QUnit.test('zoomTo flipped with ref point', function(assert) { + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + + var expectedFlippedCenters = [ + new OpenSeadragon.Point(5, 5), + new OpenSeadragon.Point(4.7505, 4.7505), + new OpenSeadragon.Point(4.6005, 4.7505), + new OpenSeadragon.Point(4.8455, 4.9955), + new OpenSeadragon.Point(5.2205, 5.3705), + new OpenSeadragon.Point(5.2205, 5.3705), + ]; + + for (var i = 0; i < testZoomLevels.length; i++) { + viewport.zoomTo(testZoomLevels[i], testPoints[i], true); + assert.propEqual( + viewport.getZoom(), + testZoomLevels[i], + "Zoomed flipped to the correct level." + ); + assertPointsEquals( + assert, + expectedFlippedCenters[i], + viewport.getCenter(), + 1e-14, + "Panned flipped to the correct location." + ); + } + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + QUnit.test('rotation', function(assert){ var done = assert.async(); var openHandler = function(event) { @@ -890,6 +1068,28 @@ assert.propEqual(viewport.getRotation, 90, "Rotation should be 90 degrees"); viewport.setRotation(-75); assert.propEqual(viewport.getRotation, -75, "Rotation should be -75 degrees"); + + done(); + }; + + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + QUnit.test('rotation (flipped)', function(assert){ + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.setFlip(true); + + assert.propEqual(viewport.getRotation, 0, "Original flipped rotation should be 0 degrees"); + viewport.setRotation(90); + assert.propEqual(viewport.getRotation, 90, "Flipped rotation should be 90 degrees"); + viewport.setRotation(-75); + assert.propEqual(viewport.getRotation, -75, "Flipped rotation should be -75 degrees"); + done(); }; @@ -1140,4 +1340,44 @@ }); }); + QUnit.test('toggleFlipState', function(assert) { + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + assert.deepEqual(viewport.getFlip(), false, "Get original flip state should be false"); + + viewport.toggleFlip(); + assert.deepEqual(viewport.getFlip(), true, "Toggling flip state variable, viewport should be true"); + + viewport.toggleFlip(); + assert.deepEqual(viewport.getFlip(), false, "Toggling back flip state variable, viewport should be false again"); + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + QUnit.test('setFlipState', function(assert) { + var done = assert.async(); + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + assert.deepEqual(viewport.getFlip(), false, "Get original flip state should be false"); + + viewport.setFlip(true); + assert.deepEqual(viewport.getFlip(), true, "Setting flip state variable should be true"); + + viewport.setFlip(false); + assert.deepEqual(viewport.getFlip(), false, "Unsetting flip state variable, viewport should be false again"); + + done(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + })();