diff --git a/src/drawer.js b/src/drawer.js index 90fa6572..de94356c 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -407,6 +407,10 @@ $.Drawer.prototype = { } return loading; + }, + + canRotate: function() { + return USE_CANVAS; } }; @@ -509,6 +513,7 @@ function updateViewport( drawer ) { Math.log( 2 ) )) ), + degrees = drawer.viewport.degrees, renderPixelRatioC, renderPixelRatioT, zeroRatioT, @@ -533,7 +538,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; @@ -575,6 +587,7 @@ function updateViewport( drawer ) { continue; } + //Perform calculations for draw if we haven't drawn this renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints( drawer.source.getPixelRatio( level ), false @@ -1117,7 +1130,7 @@ function drawOverlay( viewport, overlay, container ){ overlay.bounds.getSize(), true ); - overlay.drawHTML( container ); + overlay.drawHTML( container, viewport ); } function drawTiles( drawer, lastDrawn ){ @@ -1196,7 +1209,15 @@ function drawTiles( drawer, lastDrawn ){ } else { if ( USE_CANVAS ) { - tile.drawCanvas( drawer.context ); + // TODO do this in a more performant way + // 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 ); + restoreRotationChanges( tile, drawer.canvas, drawer.context ); + } else { + tile.drawCanvas( drawer.context ); + } } else { tile.drawHTML( drawer.canvas ); } @@ -1222,6 +1243,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..e5913772 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', diff --git a/src/overlay.js b/src/overlay.js index a0e40490..b828ac9e 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -174,12 +174,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 @@ -200,6 +206,23 @@ position = position.apply( Math.floor ); size = size.apply( Math.ceil ); + // 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 ); + + 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) { diff --git a/src/point.js b/src/point.js index 9d332977..806f9fdb 100644 --- a/src/point.js +++ b/src/point.js @@ -160,6 +160,21 @@ $.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 = 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 ); + }, + /** * Add another Point to this point and return a new Point. * @function diff --git a/src/rectangle.js b/src/rectangle.js index 05921252..a3b1c7dd 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,62 @@ $.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; + + 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 new $.Rect( + this.x, + this.y, + this.width, + this.height + ); + } + + 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/viewport.js b/src/viewport.js index a0a7bda8..8cc1ef8d 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 ); @@ -500,6 +501,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 ); }, @@ -534,6 +536,12 @@ $.Viewport.prototype = { * @return {OpenSeadragon.Viewport} Chainable. */ zoomBy: function( factor, refPoint, immediately ) { + if( refPoint ) { + 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 ); }, @@ -565,6 +573,40 @@ $.Viewport.prototype = { return this; }, + /** + * 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. + */ + setRotation: function( degrees ) { + if( !( this.viewer && this.viewer.drawer.canRotate() ) ) { + return this; + } + + 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; + }, + + /** + * 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.