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 ); },