diff --git a/src/drawer.js b/src/drawer.js index 9e928bb6..f3abc1aa 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -267,13 +267,14 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ }, /** - * Translates from OpenSeadragon viewer rectangle to drawer rectangle. + * Scale from OpenSeadragon viewer rectangle to drawer rectangle + * (ignoring rotation) * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system. * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system. */ viewportToDrawerRectangle: function(rectangle) { - var topLeft = this.viewport.pixelFromPoint(rectangle.getTopLeft(), true); - var size = this.viewport.deltaPixelsFromPoints(rectangle.getSize(), true); + var topLeft = this.viewport.scalePixelFromPoint(rectangle.getTopLeft(), true); + var size = this.viewport.scaleDeltaPixelsFromPoints(rectangle.getSize(), true); return new $.Rect( topLeft.x * $.pixelDensityRatio, @@ -293,22 +294,14 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1. * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position */ - drawTile: function( tile, drawingHandler, useSketch, scale, translate ) { + drawTile: function(tile, drawingHandler, useSketch, scale, translate) { $.console.assert(tile, '[Drawer.drawTile] tile is required'); $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required'); - if ( this.useCanvas ) { - var context = this._getContext( useSketch ); + if (this.useCanvas) { + var context = this._getContext(useSketch); scale = scale || 1; - // TODO do this in a more performant way - // specifically, don't save,rotate,restore every time we draw a tile - if( this.viewport.degrees !== 0 ) { - this._offsetForRotation( tile, this.viewport.degrees, useSketch ); - tile.drawCanvas( context, drawingHandler, scale, translate ); - this._restoreRotationChanges( tile, useSketch ); - } else { - tile.drawCanvas( context, drawingHandler, scale, translate ); - } + tile.drawCanvas(context, drawingHandler, scale, translate); } else { tile.drawHTML( this.canvas ); } @@ -418,7 +411,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ context.fillStyle = this.debugGridColor; if ( this.viewport.degrees !== 0 ) { - this._offsetForRotation( tile, this.viewport.degrees ); + this._offsetForRotation(this.viewport.degrees); } context.strokeRect( @@ -480,7 +473,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ ); if ( this.viewport.degrees !== 0 ) { - this._restoreRotationChanges( tile ); + this._restoreRotationChanges(); } context.restore(); }, @@ -506,21 +499,21 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ }, // private - _offsetForRotation: function( tile, degrees, useSketch ){ - var cx = this.canvas.width / 2, - cy = this.canvas.height / 2; + _offsetForRotation: function(degrees, useSketch) { + var cx = this.canvas.width / 2; + var cy = this.canvas.height / 2; - var context = this._getContext( useSketch ); + var context = this._getContext(useSketch); context.save(); context.translate(cx, cy); - context.rotate( Math.PI / 180 * degrees); + context.rotate(Math.PI / 180 * degrees); context.translate(-cx, -cy); }, // private - _restoreRotationChanges: function( tile, useSketch ){ - var context = this._getContext( useSketch ); + _restoreRotationChanges: function(useSketch) { + var context = this._getContext(useSketch); context.restore(); }, diff --git a/src/navigator.js b/src/navigator.js index 7addc5ea..a2244632 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -307,8 +307,8 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* if( viewport && this.viewport ) { bounds = viewport.getBounds( true ); - topleft = this.viewport.pixelFromPoint( bounds.getTopLeft(), false ); - bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight(), false ) + topleft = this.viewport.scalePixelFromPoint(bounds.getTopLeft(), false); + bottomright = this.viewport.scalePixelFromPoint(bounds.getBottomRight(), false) .minus( this.totalBorderWidths ); //update style for navigator-box diff --git a/src/point.js b/src/point.js index 1ceef296..cf31c44a 100644 --- a/src/point.js +++ b/src/point.js @@ -179,14 +179,18 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ * 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. + * @param {OpenSeadragon.Point} [pivot=(0,0)] Point around which to rotate. + * Defaults to the origin. * @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 ); + rotate: function (degrees, pivot) { + pivot = pivot || new $.Point(0, 0); + var angle = degrees * Math.PI / 180.0; + var cos = Math.cos(angle); + var sin = Math.sin(angle); + var x = cos * (this.x - pivot.x) - sin * (this.y - pivot.y) + pivot.x; + var y = sin * (this.x - pivot.x) + cos * (this.y - pivot.y) + pivot.y; + return new $.Point(x, y); }, /** diff --git a/src/rectangle.js b/src/rectangle.js index 35544cdf..2cfd4060 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -351,7 +351,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ (Math.round(this.y * 100) / 100) + "," + (Math.round(this.width * 100) / 100) + "x" + (Math.round(this.height * 100) / 100) + "," + - (Math.round(this.degrees * 100) / 100) + "°" + + (Math.round(this.degrees * 100) / 100) + "deg" + "]"; } }; diff --git a/src/tiledimage.js b/src/tiledimage.js index b7b85995..2d207af6 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -357,23 +357,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport. */ imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight, current ) { - if (imageX instanceof $.Rect) { + var rect = imageX; + if (rect instanceof $.Rect) { //they passed a rect instead of individual components current = imageY; - pixelWidth = imageX.width; - pixelHeight = imageX.height; - imageY = imageX.y; - imageX = imageX.x; + } else { + rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight); } - var coordA = this.imageToViewportCoordinates(imageX, imageY, current); - var coordB = this._imageToViewportDelta(pixelWidth, pixelHeight, current); + var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current); + var coordB = this._imageToViewportDelta(rect.width, rect.height, current); return new $.Rect( coordA.x, coordA.y, coordB.x, - coordB.y + coordB.y, + rect.degrees ); }, @@ -389,23 +389,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image. */ viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) { + var rect = viewerX; if (viewerX instanceof $.Rect) { //they passed a rect instead of individual components current = viewerY; - pointWidth = viewerX.width; - pointHeight = viewerX.height; - viewerY = viewerX.y; - viewerX = viewerX.x; + } else { + rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight); } - var coordA = this.viewportToImageCoordinates(viewerX, viewerY, current); - var coordB = this._viewportToImageDelta(pointWidth, pointHeight, current); + var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current); + var coordB = this._viewportToImageDelta(rect.width, rect.height, current); return new $.Rect( coordA.x, coordA.y, coordB.x, - coordB.y + coordB.y, + rect.degrees ); }, @@ -652,7 +652,7 @@ function updateViewport( tiledImage ) { haveDrawn = false, currentTime = $.now(), viewportBounds = tiledImage.viewport.getBoundsWithMargins( true ), - zeroRatioC = tiledImage.viewport.deltaPixelsFromPoints( + zeroRatioC = tiledImage.viewport.scaleDeltaPixelsFromPoints( tiledImage.source.getPixelRatio( 0 ), true ).x * tiledImage._scaleSpring.current.value, @@ -733,7 +733,7 @@ function updateViewport( tiledImage ) { drawLevel = false; //Avoid calculations for draw if we have already drawn this - renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPoints( + renderPixelRatioC = tiledImage.viewport.scaleDeltaPixelsFromPoints( tiledImage.source.getPixelRatio( level ), true ).x * tiledImage._scaleSpring.current.value; @@ -747,12 +747,12 @@ function updateViewport( tiledImage ) { } //Perform calculations for draw if we haven't drawn this - renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPoints( + renderPixelRatioT = tiledImage.viewport.scaleDeltaPixelsFromPoints( tiledImage.source.getPixelRatio( level ), false ).x * tiledImage._scaleSpring.current.value; - zeroRatioT = tiledImage.viewport.deltaPixelsFromPoints( + zeroRatioT = tiledImage.viewport.scaleDeltaPixelsFromPoints( tiledImage.source.getPixelRatio( Math.max( tiledImage.source.getClosestLevel( tiledImage.viewport.containerSize ) - 1, @@ -1140,10 +1140,10 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, boundsSize.x *= tiledImage._scaleSpring.current.value; boundsSize.y *= tiledImage._scaleSpring.current.value; - var positionC = viewport.pixelFromPoint( boundsTL, true ), - positionT = viewport.pixelFromPoint( boundsTL, false ), - sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ), - sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ), + var positionC = viewport.scalePixelFromPoint(boundsTL, true), + positionT = viewport.scalePixelFromPoint(boundsTL, false), + sizeC = viewport.scaleDeltaPixelsFromPoints(boundsSize, true), + sizeT = viewport.scaleDeltaPixelsFromPoints(boundsSize, false), tileCenter = positionT.plus( sizeT.divide( 2 ) ), tileDistance = viewportCenter.distanceTo( tileCenter ); @@ -1331,6 +1331,10 @@ function drawTiles( tiledImage, lastDrawn ) { tiledImage._drawer._clear( true ); } + if (tiledImage.viewport.degrees !== 0) { + tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, useSketch); + } + var usedClip = false; if ( tiledImage._clip ) { tiledImage._drawer.saveContext(useSketch); @@ -1396,6 +1400,10 @@ function drawTiles( tiledImage, lastDrawn ) { tiledImage._drawer.restoreContext( useSketch ); } + if (tiledImage.viewport.degrees !== 0) { + tiledImage._drawer._restoreRotationChanges(useSketch); + } + if ( useSketch ) { tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale, sketchTranslate ); } diff --git a/src/viewport.js b/src/viewport.js index b69ccbcb..2ad7b084 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -704,7 +704,6 @@ $.Viewport.prototype = /** @lends OpenSeadragon.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 ); }, @@ -750,14 +749,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @return {OpenSeadragon.Viewport} Chainable. * @fires OpenSeadragon.Viewer.event:zoom */ - zoomBy: function( factor, refPoint, immediately ) { - if( refPoint instanceof $.Point && !isNaN( refPoint.x ) && !isNaN( refPoint.y ) ) { - 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 ); + zoomBy: function(factor, refPoint, immediately) { + return this.zoomTo( + this.zoomSpring.target.value * factor, refPoint, immediately); }, /** @@ -936,40 +930,79 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ return changed; }, + /** + * Scale a delta (translation vector) from pixels coordinates to viewport + * coordinates. This method does not take rotation into account. + * Consider using deltaPixelsFromPoints if you need to account for rotation. + * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert. + * @param {Boolean} [current=false] - Pass true for the current location; + * defaults to false (target location). + * @returns {OpenSeadragon.Point} + */ + scaleDeltaPixelsFromPoints: function(deltaPoints, current) { + return deltaPoints.times( + this._containerInnerSize.x * this.getZoom(current) + ); + }, /** - * Convert a delta (translation vector) from pixels coordinates to viewport coordinates - * @function - * @param {Boolean} current - Pass true for the current location; defaults to false (target location). + * Convert a delta (translation vector) from pixels coordinates to viewport + * coordinates. + * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert. + * @param {Boolean} [current=false] - Pass true for the current location; + * defaults to false (target location). + * @returns {OpenSeadragon.Point} */ - deltaPixelsFromPoints: function( deltaPoints, current ) { - return deltaPoints.times( - this._containerInnerSize.x * this.getZoom( current ) + deltaPixelsFromPoints: function(deltaPoints, current) { + return this.scaleDeltaPixelsFromPoints( + deltaPoints.rotate(this.getRotation()), + current); + }, + + /** + * Scale a delta (translation vector) from viewport coordinates to pixels + * coordinates. This method does not take rotation into account. + * Consider using deltaPointsFromPixels if you need to account for rotation. + * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert. + * @param {Boolean} [current=false] - Pass true for the current location; + * defaults to false (target location). + * @returns {OpenSeadragon.Point} + */ + scaleDeltaPointsFromPixels: function(deltaPixels, current) { + return deltaPixels.divide( + this._containerInnerSize.x * this.getZoom(current) ); }, /** * Convert a delta (translation vector) from viewport coordinates to pixels coordinates. - * @function - * @param {Boolean} current - Pass true for the current location; defaults to false (target location). + * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert. + * @param {Boolean} [current=false] - Pass true for the current location; + * defaults to false (target location). + * @returns {OpenSeadragon.Point} */ - deltaPointsFromPixels: function( deltaPixels, current ) { - return deltaPixels.divide( - this._containerInnerSize.x * this.getZoom( current ) - ); + deltaPointsFromPixels: function(deltaPixels, current) { + return this.scaleDeltaPointsFromPixels(deltaPixels, current) + .rotate(-this.getRotation()); + }, + + scalePixelFromPoint: function(point, current) { + return this._scalePixelFromPoint(point, this.getBounds(current)); }, /** * Convert image pixel coordinates to viewport coordinates. * @function - * @param {Boolean} current - Pass true for the current location; defaults to false (target location). + * @param {Boolean} [current=false] - Pass true for the current location; + * defaults to false (target location). + * @param {Boolean} [ignoreRotation=false] - Pass true to ignore the rotation */ - pixelFromPoint: function( point, current ) { - return this._pixelFromPoint(point, this.getBounds( current )); + pixelFromPoint: function(point, current) { + return this._pixelFromPoint(point, this.getBounds(current)); }, // private - _pixelFromPoint: function( point, bounds ) { + _scalePixelFromPoint: function(point, bounds) { return point.minus( bounds.getTopLeft() ).times( @@ -979,12 +1012,14 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ ); }, - /** - * Convert viewport coordinates to image pixel coordinates. - * @function - * @param {Boolean} current - Pass true for the current location; defaults to false (target location). - */ - pointFromPixel: function( pixel, current ) { + // private + _pixelFromPoint: function(point, bounds) { + return this._scalePixelFromPoint( + point.rotate(this.getRotation(), this.getCenter(true)), + bounds); + }, + + scalePointFromPixel: function(pixel, current) { var bounds = this.getBounds( current ); return pixel.minus( new $.Point(this._margins.left, this._margins.top) @@ -995,6 +1030,18 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ ); }, + /** + * Convert viewport coordinates to image pixel coordinates. + * @function + * @param {Boolean} current - Pass true for the current location; defaults to false (target location). + */ + pointFromPixel: function(pixel, current) { + return this.scalePointFromPixel(pixel, current).rotate( + -this.getRotation(), + this.getCenter(true) + ); + }, + // private _viewportToImageDelta: function( viewerX, viewerY ) { var scale = this.homeBounds.width; @@ -1075,29 +1122,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @param {Number} pixelWidth the width in pixel of the rectangle. * @param {Number} pixelHeight the height in pixel of the rectangle. */ - imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) { - var coordA, - coordB, - rect; - if( arguments.length == 1 ) { - //they passed a rectangle instead of individual components - rect = imageX; - return this.imageToViewportRectangle( - rect.x, rect.y, rect.width, rect.height - ); + imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) { + var rect = imageX; + if (!(rect instanceof $.Rect)) { + //they passed individual components instead of a rectangle + rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight); } - coordA = this.imageToViewportCoordinates( - imageX, imageY - ); - coordB = this._imageToViewportDelta( - pixelWidth, pixelHeight - ); + var coordA = this.imageToViewportCoordinates(rect.x, rect.y); + var coordB = this._imageToViewportDelta(rect.width, rect.height); return new $.Rect( coordA.x, coordA.y, coordB.x, - coordB.y + coordB.y, + rect.degrees ); }, @@ -1116,25 +1155,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @param {Number} pointWidth the width of the rectangle in viewport coordinate system. * @param {Number} pointHeight the height of the rectangle in viewport coordinate system. */ - viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) { - var coordA, - coordB, - rect; - if ( arguments.length == 1 ) { - //they passed a rectangle instead of individual components - rect = viewerX; - return this.viewportToImageRectangle( - rect.x, rect.y, rect.width, rect.height - ); + viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) { + var rect = viewerX; + if (!(rect instanceof $.Rect)) { + //they passed individual components instead of a rectangle + rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight); } - coordA = this.viewportToImageCoordinates( viewerX, viewerY ); - coordB = this._viewportToImageDelta(pointWidth, pointHeight); + var coordA = this.viewportToImageCoordinates(rect.x, rect.y); + var coordB = this._viewportToImageDelta(rect.width, rect.height); return new $.Rect( coordA.x, coordA.y, coordB.x, - coordB.y + coordB.y, + rect.degrees ); }, diff --git a/test/demo/coordinates.html b/test/demo/coordinates.html index 188e87ee..3cf836dd 100644 --- a/test/demo/coordinates.html +++ b/test/demo/coordinates.html @@ -24,38 +24,55 @@