diff --git a/changelog.txt b/changelog.txt index b7086bf9..551c5bf2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,6 +12,9 @@ OPENSEADRAGON CHANGELOG * Added 'ABSOLUTE' as a navigatorPosition option, along with corresponding navigatorTop, navigatorLeft options. Allows the navigator minimap to be placed anywhere in the viewer (#310) * Enhanced the navigatorTop, navigatorLeft, navigatorHeight, and navigatorWidth options to allow a number for pixel units or a string for other element units (%, em, etc.) (#310) * Additional enhancements for IIIF support (#315) +* Fixed: Setting degrees in Viewer constructor has no effect (#336) +* Added pre-draw event for tiles to allow applications to alter the image (#348) +* Added optional Rotate Left/Right buttons to standard controls (#341) 1.0.0: diff --git a/images/rotateleft_grouphover.png b/images/rotateleft_grouphover.png new file mode 100644 index 00000000..9aec7ac9 Binary files /dev/null and b/images/rotateleft_grouphover.png differ diff --git a/images/rotateleft_hover.png b/images/rotateleft_hover.png new file mode 100644 index 00000000..ba32c5a4 Binary files /dev/null and b/images/rotateleft_hover.png differ diff --git a/images/rotateleft_pressed.png b/images/rotateleft_pressed.png new file mode 100644 index 00000000..b968ebf8 Binary files /dev/null and b/images/rotateleft_pressed.png differ diff --git a/images/rotateleft_rest.png b/images/rotateleft_rest.png new file mode 100644 index 00000000..ebbf081b Binary files /dev/null and b/images/rotateleft_rest.png differ diff --git a/images/rotateright_grouphover.png b/images/rotateright_grouphover.png new file mode 100644 index 00000000..86e8689c Binary files /dev/null and b/images/rotateright_grouphover.png differ diff --git a/images/rotateright_hover.png b/images/rotateright_hover.png new file mode 100644 index 00000000..d22a728f Binary files /dev/null and b/images/rotateright_hover.png differ diff --git a/images/rotateright_pressed.png b/images/rotateright_pressed.png new file mode 100644 index 00000000..fc2ead64 Binary files /dev/null and b/images/rotateright_pressed.png differ diff --git a/images/rotateright_rest.png b/images/rotateright_rest.png new file mode 100644 index 00000000..07219678 Binary files /dev/null and b/images/rotateright_rest.png differ diff --git a/src/drawer.js b/src/drawer.js index ef727bec..70cb1a84 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -1017,6 +1017,25 @@ function drawTiles( drawer, lastDrawn ){ tileSource, collectionTileSource; + // We need a callback to give image manipulation a chance to happen + var drawingHandler = function(args) { + if (drawer.viewer) { + /** + * This event is fired just before the tile is drawn giving the application a chance to alter the image. + * + * NOTE: This event is only fired when the drawer is using a . + * + * @event tile-drawing + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - 'context', 'tile' and 'rendered'. + */ + drawer.viewer.raiseEvent('tile-drawing', args); + } + }; + for ( i = lastDrawn.length - 1; i >= 0; i-- ) { tile = lastDrawn[ i ]; @@ -1088,10 +1107,10 @@ function drawTiles( drawer, lastDrawn ){ // specifically, don't save,rotate,restore every time we draw a tile if( drawer.viewport.degrees !== 0 ) { offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); - tile.drawCanvas( drawer.context ); + tile.drawCanvas( drawer.context, drawingHandler ); restoreRotationChanges( tile, drawer.canvas, drawer.context ); } else { - tile.drawCanvas( drawer.context ); + tile.drawCanvas( drawer.context, drawingHandler ); } } else { tile.drawHTML( drawer.canvas ); diff --git a/src/openseadragon.js b/src/openseadragon.js index 4532ddbf..a151f7e3 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -358,6 +358,11 @@ * image and if the 'next' button will wrap to the first image when viewing * the last image. * + * @property {Boolean} [showRotationControl=false] + * If true then the rotate left/right controls will be displayed as part of the + * standard controls. This is also subject to the browser support for rotate + * (e.g. viewer.drawer.canRotate()). + * * @property {Boolean} [showSequenceControl=true] * If the viewer has been configured with a sequence of tile sources, then * provide buttons for navigating forward and backward through the images. @@ -815,6 +820,18 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ HOVER: 'fullpage_hover.png', DOWN: 'fullpage_pressed.png' }, + rotateleft: { + REST: 'rotateleft_rest.png', + GROUP: 'rotateleft_grouphover.png', + HOVER: 'rotateleft_hover.png', + DOWN: 'rotateleft_pressed.png' + }, + rotateright: { + REST: 'rotateright_rest.png', + GROUP: 'rotateright_grouphover.png', + HOVER: 'rotateright_hover.png', + DOWN: 'rotateright_pressed.png' + }, previous: { REST: 'previous_rest.png', GROUP: 'previous_grouphover.png', @@ -829,6 +846,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ } }, navPrevNextWrap: false, + showRotationControl: false, //DEVELOPER SETTINGS debugMode: false, diff --git a/src/strings.js b/src/strings.js index e6cf8352..03f4ed42 100644 --- a/src/strings.js +++ b/src/strings.js @@ -55,7 +55,9 @@ var I18N = { ZoomIn: "Zoom in", ZoomOut: "Zoom out", NextPage: "Next page", - PreviousPage: "Previous page" + PreviousPage: "Previous page", + RotateLeft: "Rotate left", + RotateRight: "Rotate right" } }; diff --git a/src/tile.js b/src/tile.js index d818c010..9aa4bde4 100644 --- a/src/tile.js +++ b/src/tile.js @@ -231,8 +231,10 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{ * Renders the tile in a canvas-based context. * @function * @param {Canvas} context + * @param {Function} method for firing the drawing event. drawingHandler({context, tile, rendered}) + * where rendered is the context with the pre-drawn image. */ - drawCanvas: function( context ) { + drawCanvas: function( context, drawingHandler ) { var position = this.position, size = this.size, @@ -280,6 +282,9 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{ rendered = TILE_CACHE[ this.url ]; + // This gives the application a chance to make image manipulation changes as we are rendering the image + drawingHandler({context: context, tile: this, rendered: rendered}); + //rendered.save(); context.drawImage( rendered.canvas, diff --git a/src/viewer.js b/src/viewer.js index b0c28b68..157e6ef1 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1150,6 +1150,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ), onHomeHandler = $.delegate( this, onHome ), onFullScreenHandler = $.delegate( this, onFullScreen ), + onRotateLeftHandler = $.delegate( this, onRotateLeft ), + onRotateRightHandler = $.delegate( this, onRotateRight ), onFocusHandler = $.delegate( this, onFocus ), onBlurHandler = $.delegate( this, onBlur ), navImages = this.navImages, @@ -1229,6 +1231,37 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, onBlur: onBlurHandler })); + if (this.showRotationControl) { + buttons.push( this.rotateLeft = new $.Button({ + element: this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null, + clickTimeThreshold: this.clickTimeThreshold, + clickDistThreshold: this.clickDistThreshold, + tooltip: $.getString( "Tooltips.RotateLeft" ), + srcRest: resolveUrl( this.prefixUrl, navImages.rotateleft.REST ), + srcGroup: resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ), + srcHover: resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ), + srcDown: resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ), + onRelease: onRotateLeftHandler, + onFocus: onFocusHandler, + onBlur: onBlurHandler + })); + + buttons.push( this.rotateRight = new $.Button({ + element: this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null, + clickTimeThreshold: this.clickTimeThreshold, + clickDistThreshold: this.clickDistThreshold, + tooltip: $.getString( "Tooltips.RotateRight" ), + srcRest: resolveUrl( this.prefixUrl, navImages.rotateright.REST ), + srcGroup: resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ), + srcHover: resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ), + srcDown: resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ), + onRelease: onRotateRightHandler, + onFocus: onFocusHandler, + onBlur: onBlurHandler + })); + + } + if( useGroup ){ this.buttons = new $.ButtonGroup({ buttons: buttons, @@ -1550,7 +1583,8 @@ function _getSafeElemSize (oElement) { * @private */ function openTileSource( viewer, source ) { - var _this = viewer; + var i, + _this = viewer; if ( _this.source ) { _this.close( ); @@ -1578,7 +1612,8 @@ function openTileSource( viewer, source ) { showNavigator: false, minZoomImageRatio: 1, maxZoomPixelRatio: 1, - viewer: _this //, + viewer: _this, + degrees: _this.degrees //, //TODO: figure out how to support these in a way that makes sense //minZoomLevel: this.minZoomLevel, //maxZoomLevel: this.maxZoomLevel @@ -1600,7 +1635,8 @@ function openTileSource( viewer, source ) { defaultZoomLevel: _this.defaultZoomLevel, minZoomLevel: _this.minZoomLevel, maxZoomLevel: _this.maxZoomLevel, - viewer: _this + viewer: _this, + degrees: _this.degrees }); } @@ -1629,6 +1665,21 @@ function openTileSource( viewer, source ) { debugGridColor: _this.debugGridColor }); + // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons + if (!_this.drawer.canRotate()) { + // Disable/remove the rotate left/right buttons since they aren't supported + if (_this.rotateLeft) { + i = _this.buttons.buttons.indexOf(_this.rotateLeft); + _this.buttons.buttons.splice(i, 1); + _this.buttons.element.removeChild(_this.rotateLeft.element); + } + if (_this.rotateRight) { + i = _this.buttons.buttons.indexOf(_this.rotateRight); + _this.buttons.buttons.splice(i, 1); + _this.buttons.element.removeChild(_this.rotateRight.element); + } + } + //Instantiate a navigator if configured if ( _this.showNavigator && !_this.collectionMode ){ // Note: By passing the fully parsed source, the navigator doesn't @@ -2335,6 +2386,38 @@ function onFullScreen() { } } +/** + * Note: The current rotation feature is limited to 90 degree turns. + */ +function onRotateLeft() { + if ( this.viewport ) { + var currRotation = this.viewport.getRotation(); + if (currRotation === 0) { + currRotation = 270; + } + else { + currRotation -= 90; + } + this.viewport.setRotation(currRotation); + } +} + +/** + * Note: The current rotation feature is limited to 90 degree turns. + */ +function onRotateRight() { + if ( this.viewport ) { + var currRotation = this.viewport.getRotation(); + if (currRotation === 270) { + currRotation = 0; + } + else { + currRotation += 90; + } + this.viewport.setRotation(currRotation); + } +} + function onPrevious(){ var previous = THIS[ this.hash ].sequence - 1; diff --git a/test/events.js b/test/events.js index 6fb7f23c..86f007c1 100644 --- a/test/events.js +++ b/test/events.js @@ -280,4 +280,23 @@ viewer.open( '/test/data/testpattern.dzi' ); } ); + // ---------- + asyncTest( 'tile-drawing event', function () { + var tileDrawing = function ( event ) { + viewer.removeHandler( 'tile-drawing', tileDrawing ); + ok( event, 'Event handler should be invoked' ); + if ( event ) { + // Make sure we have the expected elements set + ok(event.context, "Context should be set"); + ok(event.tile, "Tile should be set"); + ok(event.rendered, "Rendered should be set"); + } + viewer.close(); + start(); + }; + + viewer.addHandler( 'tile-drawing', tileDrawing ); + viewer.open( '/test/data/testpattern.dzi' ); + } ); + } )(); diff --git a/test/rotate.js b/test/rotate.js new file mode 100644 index 00000000..9128d7fa --- /dev/null +++ b/test/rotate.js @@ -0,0 +1,77 @@ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ + +(function () { + var viewer; + + module('Basic', { + setup: function () { + var example = $('
').appendTo("#qunit-fixture"); + + testLog.reset(); + + }, + teardown: function () { + if (viewer && viewer.close) { + viewer.close(); + } + + viewer = null; + } + }); + + asyncTest('RotateControlOff', function () { + + var openHandler = function (event) { + viewer.removeHandler('open', openHandler); + ok(true, 'Open event was sent'); + ok(viewer.drawer, 'Drawer exists'); + ok(viewer.drawer.canRotate(), 'drawer.canRotate needs to be true'); + ok(!viewer.showRotationControl, 'showRotationControl should be off'); + ok(!viewer.rotateLeft, "rotateLeft button should be null"); + ok(!viewer.rotateRight, "rotateRight button should be null"); + start(); + }; + + viewer = OpenSeadragon({ + id: 'rotateTests', + prefixUrl: '/build/openseadragon/images/', + springStiffness: 100, // Faster animation = faster tests + showRotationControl: false + }); + viewer.addHandler('open', openHandler); + viewer.open('/test/data/testpattern.dzi'); + }); + + asyncTest('RotateControlOn', function () { + + var openHandler = function (event) { + viewer.removeHandler('open', openHandler); + ok(true, 'Open event was sent'); + ok(viewer.drawer, 'Drawer exists'); + ok(viewer.drawer.canRotate(), 'drawer.canRotate needs to be true'); + ok(viewer.showRotationControl, 'showRotationControl should be true'); + ok(-1 != viewer.buttons.buttons.indexOf(viewer.rotateLeft), "rotateLeft should be found"); + ok(-1 != viewer.buttons.buttons.indexOf(viewer.rotateRight), "rotateRight should be found"); + + // Now simulate the left/right button clicks. + // TODO: re-factor simulateViewerClickWithDrag so it'll accept any element, and use that. + ok(viewer.viewport.degrees === 0, "Image should start at 0 degrees rotation"); + viewer.rotateLeft.onRelease(); + ok(viewer.viewport.degrees === 270, "Image should be 270 degrees rotation (left)"); + viewer.rotateRight.onRelease(); + ok(viewer.viewport.degrees === 0, "Image should be 270 degrees rotation (right)"); + + start(); + }; + + viewer = OpenSeadragon({ + id: 'rotateTests', + prefixUrl: '/build/openseadragon/images/', + springStiffness: 100, // Faster animation = faster tests + showRotationControl: true + }); + viewer.addHandler('open', openHandler); + viewer.open('/test/data/testpattern.dzi'); + }); + +})(); diff --git a/test/test.html b/test/test.html index 3ad8ffff..351a5c2e 100644 --- a/test/test.html +++ b/test/test.html @@ -28,5 +28,6 @@ +