From b9583c43acc12db7cc5b43ad9b5015abff7d784c Mon Sep 17 00:00:00 2001 From: Robert Hickman Date: Tue, 13 Aug 2013 15:39:22 -0600 Subject: [PATCH 1/5] Working on rotating images. So far only 90 degree rotation is supported. Only the image is currently being rotated. Overlays, debugger, and the navigator still need to be updated to support rotation. --- src/drawer.js | 41 ++++++++++++++++++++- src/openseadragon.js | 22 ++++++++++++ src/point.js | 21 +++++++++++ src/rectangle.js | 84 ++++++++++++++++++++++++++++++++++++++++---- src/viewer.js | 17 +++++++++ src/viewport.js | 10 +++++- 6 files changed, 187 insertions(+), 8 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index 75c18631..fd19b86b 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -505,6 +505,7 @@ function updateViewport( drawer ) { Math.log( 2 ) )) ), + degrees = drawer.viewport.degrees, renderPixelRatioC, renderPixelRatioT, zeroRatioT, @@ -529,7 +530,14 @@ function updateViewport( drawer ) { drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y ); } - //TODO + //Change bounds for rotation + if (degrees === 90 || degrees === 270) { + var rotatedBounds = viewportBounds.rotate( degrees ); + viewportTL = rotatedBounds.getTopLeft(); + viewportBR = rotatedBounds.getBottomRight(); + } + + //Don't draw if completely outside of the viewport if ( !drawer.wrapHorizontal && ( viewportBR.x < 0 || viewportTL.x > 1 ) ) { return; @@ -571,6 +579,7 @@ function updateViewport( drawer ) { continue; } + //Perform calculations for draw if we haven't drawn this renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints( drawer.source.getPixelRatio( level ), false @@ -1192,7 +1201,11 @@ function drawTiles( drawer, lastDrawn ){ } else { if ( USE_CANVAS ) { + // TODO do this in a more performant way + // specifically, don't save,rotate,restore every time we draw a tile + offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); tile.drawCanvas( drawer.context ); + restoreRotationChanges( tile, drawer.canvas, drawer.context ); } else { tile.drawHTML( drawer.canvas ); } @@ -1218,6 +1231,32 @@ function drawTiles( drawer, lastDrawn ){ } } +function offsetForRotation( tile, canvas, context, degrees ){ + var cx = canvas.width / 2, + cy = canvas.height / 2, + px = tile.position.x - cx, + py = tile.position.y - cy; + + context.save(); + + context.translate(cx, cy); + context.rotate( Math.PI / 180 * degrees); + tile.position.x = px; + tile.position.y = py; +} + +function restoreRotationChanges( tile, canvas, context ){ + var cx = canvas.width / 2, + cy = canvas.height / 2, + px = tile.position.x + cx, + py = tile.position.y + cy; + + tile.position.x = px; + tile.position.y = py; + + context.restore(); +} + function drawDebugInfo( drawer, tile, count, i ){ diff --git a/src/openseadragon.js b/src/openseadragon.js index 34e6eb56..59554281 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -533,6 +533,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ navigatorPosition: null, navigatorSizeRatio: 0.2, + // INITIAL ROTATION + degrees: 0, + //REFERENCE STRIP SETTINGS showReferenceStrip: false, referenceStripScroll: 'horizontal', @@ -1924,4 +1927,23 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ throw new Error(message); }; + /** + * http://stackoverflow.com/questions/246193 + * /how-do-i-round-a-number-in-javascript + * @private + * @inner + * @function + * @param {Number} num + * @param {Number} decimals + * @return {Number} + */ + $._round = function ( num, decimals ) { + var coefficient; + + decimals = decimals || 10; + coefficient = Math.pow( 10, decimals ); + + return Math.round( num * coefficient ) / coefficient; + }; + }( OpenSeadragon )); diff --git a/src/point.js b/src/point.js index 9d332977..0fb43a26 100644 --- a/src/point.js +++ b/src/point.js @@ -160,6 +160,27 @@ $.Point.prototype = { ); }, + /** + * Rotates the point around the specified pivot + * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point + * @function + * @param {Number} degress to rotate around the pivot. + * @param {OpenSeadragon.Point} pivot Point about which to rotate. + * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot + */ + rotate: function ( degrees, pivot ) { + var angle = degrees * Math.PI / 180.0, + x = $._round( + Math.cos( angle ) * ( this.x - pivot.x ) - + Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x + ), + y = $._round( + Math.sin( angle ) * ( this.x - pivot.x ) + + Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y + ); + return new $.Point( x, y ); + }, + /** * Add another Point to this point and return a new Point. * @function diff --git a/src/rectangle.js b/src/rectangle.js index 05921252..22dda5ce 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -69,14 +69,17 @@ $.Rect.prototype = { }, /** - * Provides the coordinates of the upper-left corner of the rectanglea s a + * Provides the coordinates of the upper-left corner of the rectangle as a * point. * @function * @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of * the rectangle. */ getTopLeft: function() { - return new $.Point( this.x, this.y ); + return new $.Point( + this.x, + this.y + ); }, /** @@ -93,10 +96,38 @@ $.Rect.prototype = { ); }, + /** + * Provides the coordinates of the top-right corner of the rectangle as a + * point. + * @function + * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of + * the rectangle. + */ + getTopRight: function() { + return new $.Point( + this.x + this.width, + this.y + ); + }, + + /** + * Provides the coordinates of the bottom-left corner of the rectangle as a + * point. + * @function + * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of + * the rectangle. + */ + getBottomLeft: function() { + return new $.Point( + this.x, + this.y + this.height + ); + }, + /** * Computes the center of the rectangle. * @function - * @returns {OpenSeadragon.Point} The center of the rectangle as represnted + * @returns {OpenSeadragon.Point} The center of the rectangle as represented * as represented by a 2-dimensional vector (x,y) */ getCenter: function() { @@ -109,7 +140,7 @@ $.Rect.prototype = { /** * Returns the width and height component as a vector OpenSeadragon.Point * @function - * @returns {OpenSeadragon.Point} The 2 dimensional vector represnting the + * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the * the width and height of the rectangle. */ getSize: function() { @@ -117,7 +148,7 @@ $.Rect.prototype = { }, /** - * Determines if two Rectanlges have equivalent components. + * Determines if two Rectangles have equivalent components. * @function * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to. * @return {Boolean} 'true' if all components are equal, otherwise 'false'. @@ -131,7 +162,48 @@ $.Rect.prototype = { }, /** - * Provides a string representation of the retangle which is useful for + * Rotates a rectangle around a point. Currently only 90, 180, and 270 + * degrees are supported. + * @function + * @param {Number} degrees The angle in degrees to rotate. + * @param {OpenSeadragon.Point} pivot The point about which to rotate. + * Defaults to the center of the rectangle. + * @return {OpenSeadragon.Rect} + */ + rotate: function( degrees, pivot ) { + // TODO support arbitrary rotation + var width = this.width, + height = this.height, + newTopLeft; + + pivot = pivot || this.getCenter(); + + switch ( degrees ) { + case 90: + newTopLeft = this.getBottomLeft(); + width = this.height; + height = this.width; + break; + case 180: + newTopLeft = this.getBottomRight(); + break; + case 270: + newTopLeft = this.getTopRight(); + width = this.height; + height = this.width; + break; + default: + newTopLeft = this.getTopLeft(); + break; + } + + newTopLeft = newTopLeft.rotate(degrees, pivot); + + return new $.Rect(newTopLeft.x, newTopLeft.y, width, height); + }, + + /** + * Provides a string representation of the rectangle which is useful for * debugging. * @function * @returns {String} A string representation of the rectangle. diff --git a/src/viewer.js b/src/viewer.js index 0c6f5969..cd06ce48 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1074,6 +1074,19 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, return this; }, + /** + * @function + * @name OpenSeadragon.Viewer.prototype.rotate + * @return {OpenSeadragon.Viewer} Chainable. + */ + rotate: function(clockwise){ + clockwise = clockwise || true; + this.viewport.degrees = ( this.viewport.degrees + (clockwise ? 90 : -90 ) + 360 ) % 360; + //this.raiseEvent( 'rotate', { viewer: this } ); + this.drawer.update(); + return this; + }, + /** * Display a message in the viewport * @function @@ -1696,5 +1709,9 @@ function onNext(){ this.goToPage( next ); } +function onRotate(){ + this.rotate(); +} + }( OpenSeadragon )); diff --git a/src/viewport.js b/src/viewport.js index e4c0ffa0..eec94bae 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -79,7 +79,8 @@ $.Viewport = function( options ) { wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel, minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel, - maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel + maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel, + degrees: $.DEFAULT_SETTINGS.degrees }, options ); @@ -497,6 +498,7 @@ $.Viewport.prototype = { this.centerSpringX.target.value, this.centerSpringY.target.value ); + delta = delta.rotate( -this.degrees, new $.Point( 0, 0 ) ); return this.panTo( center.plus( delta ), immediately ); }, @@ -531,6 +533,12 @@ $.Viewport.prototype = { * @return {OpenSeadragon.Viewport} Chainable. */ zoomBy: function( factor, refPoint, immediately ) { + if( typeof refPoint != 'undefined' ) { + refPoint = refPoint.rotate( + -this.degrees, + new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value ) + ); + } return this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately ); }, From 6c63710131f1d283ee0a757e7ea90cbea545a886 Mon Sep 17 00:00:00 2001 From: Robert Hickman Date: Wed, 14 Aug 2013 13:43:49 -0600 Subject: [PATCH 2/5] Rotating overlays. --- src/drawer.js | 2 +- src/overlay.js | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index fd19b86b..f49b44c5 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -1122,7 +1122,7 @@ function drawOverlay( viewport, overlay, container ){ overlay.bounds.getSize(), true ); - overlay.drawHTML( container ); + overlay.drawHTML( container, viewport ); } function drawTiles( drawer, lastDrawn ){ diff --git a/src/overlay.js b/src/overlay.js index 8635499f..66036c13 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -171,12 +171,18 @@ * @function * @param {Element} container */ - drawHTML: function( container ) { + drawHTML: function( container, viewport ) { var element = this.element, style = this.style, scales = this.scales, + drawerCenter = new $.Point( + viewport.viewer.drawer.canvas.width / 2, + viewport.viewer.drawer.canvas.height / 2 + ), + degrees = viewport.degrees, position, - size; + size, + overlayCenter; if ( element.parentNode != container ) { //save the source parent for later if we need it @@ -197,6 +203,19 @@ position = position.apply( Math.floor ); size = size.apply( Math.ceil ); + // rotate the position of the overlay + if(this.scales){ + overlayCenter = new $.Point( size.x / 2, size.y / 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 ) ); + } + // call the onDraw callback if there is one to allow, this allows someone to overwrite // the drawing/positioning/sizing of the overlay if (this.onDraw) { From 54e8d8c43f2a4f9bc7135ff85f951c9a898cbe42 Mon Sep 17 00:00:00 2001 From: Robert Hickman Date: Thu, 15 Aug 2013 16:15:20 -0600 Subject: [PATCH 3/5] Fixes made after first code review of rotation. --- src/drawer.js | 10 +++++++--- src/openseadragon.js | 19 ------------------- src/overlay.js | 6 +++++- src/point.js | 10 ++-------- src/rectangle.js | 9 +++++++++ src/viewer.js | 17 ----------------- src/viewport.js | 17 +++++++++++++++++ 7 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index f49b44c5..abc7969d 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -1203,9 +1203,13 @@ function drawTiles( drawer, lastDrawn ){ if ( USE_CANVAS ) { // TODO do this in a more performant way // specifically, don't save,rotate,restore every time we draw a tile - offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); - tile.drawCanvas( drawer.context ); - restoreRotationChanges( tile, drawer.canvas, drawer.context ); + if( drawer.viewport.degrees !== 0 ) { + offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); + tile.drawCanvas( drawer.context ); + restoreRotationChanges( tile, drawer.canvas, drawer.context ); + } else { + tile.drawCanvas( drawer.context ); + } } else { tile.drawHTML( drawer.canvas ); } diff --git a/src/openseadragon.js b/src/openseadragon.js index 59554281..e5913772 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1927,23 +1927,4 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ throw new Error(message); }; - /** - * http://stackoverflow.com/questions/246193 - * /how-do-i-round-a-number-in-javascript - * @private - * @inner - * @function - * @param {Number} num - * @param {Number} decimals - * @return {Number} - */ - $._round = function ( num, decimals ) { - var coefficient; - - decimals = decimals || 10; - coefficient = Math.pow( 10, decimals ); - - return Math.round( num * coefficient ) / coefficient; - }; - }( OpenSeadragon )); diff --git a/src/overlay.js b/src/overlay.js index 66036c13..f0faf96f 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -204,7 +204,11 @@ size = size.apply( Math.ceil ); // rotate the position of the overlay - if(this.scales){ + // 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 ); position = position.plus( overlayCenter ).rotate( diff --git a/src/point.js b/src/point.js index 0fb43a26..806f9fdb 100644 --- a/src/point.js +++ b/src/point.js @@ -170,14 +170,8 @@ $.Point.prototype = { */ rotate: function ( degrees, pivot ) { var angle = degrees * Math.PI / 180.0, - x = $._round( - Math.cos( angle ) * ( this.x - pivot.x ) - - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x - ), - y = $._round( - Math.sin( angle ) * ( this.x - pivot.x ) + - Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y - ); + x = Math.cos( angle ) * ( this.x - pivot.x ) - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x, + y = Math.sin( angle ) * ( this.x - pivot.x ) + Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y; return new $.Point( x, y ); }, diff --git a/src/rectangle.js b/src/rectangle.js index 22dda5ce..32fc20dc 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -176,6 +176,15 @@ $.Rect.prototype = { height = this.height, newTopLeft; + degrees = ( degrees + 360 ) % 360; + if( degrees % 90 !== 0 ) { + throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); + } + + if( degrees === 0 ){ + return this; + } + pivot = pivot || this.getCenter(); switch ( degrees ) { diff --git a/src/viewer.js b/src/viewer.js index cd06ce48..0c6f5969 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1074,19 +1074,6 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, return this; }, - /** - * @function - * @name OpenSeadragon.Viewer.prototype.rotate - * @return {OpenSeadragon.Viewer} Chainable. - */ - rotate: function(clockwise){ - clockwise = clockwise || true; - this.viewport.degrees = ( this.viewport.degrees + (clockwise ? 90 : -90 ) + 360 ) % 360; - //this.raiseEvent( 'rotate', { viewer: this } ); - this.drawer.update(); - return this; - }, - /** * Display a message in the viewport * @function @@ -1709,9 +1696,5 @@ function onNext(){ this.goToPage( next ); } -function onRotate(){ - this.rotate(); -} - }( OpenSeadragon )); diff --git a/src/viewport.js b/src/viewport.js index eec94bae..074011d3 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -570,6 +570,23 @@ $.Viewport.prototype = { return this; }, + /** + * Currently only supports 90 degree rotation. + * Currently only works with canvas. + * @function + * @name OpenSeadragon.Viewport.prototype.rotate + * @return {OpenSeadragon.Viewport} Chainable. + */ + setRotation: function(degrees){ + degrees = ( degrees + 360 ) % 360; + if(degrees % 90 !== 0){ + throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); + } + this.degrees = degrees; + this.viewer.drawer.update(); + return this; + }, + /** * @function * @return {OpenSeadragon.Viewport} Chainable. From 767c897e7fce32650cd7252dba66da435f926e42 Mon Sep 17 00:00:00 2001 From: Robert Hickman Date: Fri, 16 Aug 2013 11:32:21 -0600 Subject: [PATCH 4/5] Revisions to rotate after second code review. --- src/drawer.js | 4 ++++ src/rectangle.js | 7 ++++++- src/viewport.js | 26 +++++++++++++++++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index abc7969d..4a198e8c 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -403,6 +403,10 @@ $.Drawer.prototype = { } return loading; + }, + + canRotate: function() { + return USE_CANVAS; } }; diff --git a/src/rectangle.js b/src/rectangle.js index 32fc20dc..a3b1c7dd 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -182,7 +182,12 @@ $.Rect.prototype = { } if( degrees === 0 ){ - return this; + return new $.Rect( + this.x, + this.y, + this.width, + this.height + ); } pivot = pivot || this.getCenter(); diff --git a/src/viewport.js b/src/viewport.js index 074011d3..e79492e5 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -533,7 +533,7 @@ $.Viewport.prototype = { * @return {OpenSeadragon.Viewport} Chainable. */ zoomBy: function( factor, refPoint, immediately ) { - if( typeof refPoint != 'undefined' ) { + if( refPoint ) { refPoint = refPoint.rotate( -this.degrees, new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value ) @@ -574,19 +574,35 @@ $.Viewport.prototype = { * Currently only supports 90 degree rotation. * Currently only works with canvas. * @function - * @name OpenSeadragon.Viewport.prototype.rotate + * @name OpenSeadragon.Viewport.prototype.setRotation * @return {OpenSeadragon.Viewport} Chainable. */ - setRotation: function(degrees){ + setRotation: function( degrees ) { + if( !( this.viewer && this.viewer.drawer.canRotate() ) ) { + return this; + } + degrees = ( degrees + 360 ) % 360; - if(degrees % 90 !== 0){ + if( degrees % 90 !== 0 ) { throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); } this.degrees = degrees; - this.viewer.drawer.update(); + if( this.viewer ) { + this.viewer.drawer.update(); + } return this; }, + /** + * Gets the current rotation in degrees. + * @function + * @name OpenSeadragon.Viewport.prototype.setRotation + * @return {Number} The current rotation in degrees. + */ + getRotation: function() { + return this.degrees; + }, + /** * @function * @return {OpenSeadragon.Viewport} Chainable. From d4467477ad63c7022ba3bdf5060a5282f3a5fcc9 Mon Sep 17 00:00:00 2001 From: Robert Hickman Date: Mon, 19 Aug 2013 16:27:00 -0600 Subject: [PATCH 5/5] More rotation documentation. --- src/viewport.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index e79492e5..4d994fd9 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -571,8 +571,10 @@ $.Viewport.prototype = { }, /** - * Currently only supports 90 degree rotation. - * Currently only works with canvas. + * Currently only 90 degree rotation is supported and it only works + * with the canvas. Additionally, the navigator does not rotate yet, + * debug mode doesn't rotate yet, and overlay rotation is only + * partially supported. * @function * @name OpenSeadragon.Viewport.prototype.setRotation * @return {OpenSeadragon.Viewport} Chainable. @@ -587,9 +589,8 @@ $.Viewport.prototype = { throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); } this.degrees = degrees; - if( this.viewer ) { - this.viewer.drawer.update(); - } + this.viewer.drawer.update(); + return this; },