diff --git a/src/drawer.js b/src/drawer.js index 661663d1..3c4a58b1 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -464,7 +464,7 @@ $.Drawer.prototype = { }, // private - drawDebugInfo: function( tile, count, i ){ + drawDebugInfo: function(tile, count, i, tiledImage) { if ( !this.useCanvas ) { return; } @@ -479,6 +479,12 @@ $.Drawer.prototype = { if ( this.viewport.degrees !== 0 ) { this._offsetForRotation(this.viewport.degrees); } + if (tiledImage.degrees) { + this._offsetForRotation( + tiledImage.degrees, + tiledImage.viewport.pixelFromPointNoRotate( + tiledImage.getBounds(true).getTopLeft(), true)); + } context.strokeRect( tile.position.x * $.pixelDensityRatio, @@ -541,6 +547,9 @@ $.Drawer.prototype = { if ( this.viewport.degrees !== 0 ) { this._restoreRotationChanges(); } + if (tiledImage.degrees) { + this._restoreRotationChanges(); + } context.restore(); }, @@ -574,17 +583,19 @@ $.Drawer.prototype = { return new $.Point(canvas.width, canvas.height); }, - // private - _offsetForRotation: function(degrees, useSketch) { - var cx = this.canvas.width / 2; - var cy = this.canvas.height / 2; + getCanvasCenter: function() { + return new $.Point(this.canvas.width / 2, this.canvas.height / 2); + }, + // private + _offsetForRotation: function(degrees, point, useSketch) { + point = point || this.getCanvasCenter(); var context = this._getContext(useSketch); context.save(); - context.translate(cx, cy); + context.translate(point.x, point.y); context.rotate(Math.PI / 180 * degrees); - context.translate(-cx, -cy); + context.translate(-point.x, -point.y); }, // private diff --git a/src/openseadragon.js b/src/openseadragon.js index de7399e8..bd252e1c 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1375,6 +1375,21 @@ function OpenSeadragon( options ){ return string.charAt(0).toUpperCase() + string.slice(1); }, + /** + * Compute the modulo of a number but makes sure to always return + * a positive value. + * @param {Number} number the number to computes the modulo of + * @param {Number} modulo the modulo + * @returns {Number} the result of the modulo of number + */ + positiveModulo: function(number, modulo) { + var result = number % modulo; + if (result < 0) { + result += modulo; + } + return result; + }, + /** * Determines if a point is within the bounding rectangle of the given element (hit-test). * @function diff --git a/src/point.js b/src/point.js index 7d4f6740..113f7036 100644 --- a/src/point.js +++ b/src/point.js @@ -190,10 +190,7 @@ $.Point.prototype = { var sin; // Avoid float computations when possible if (degrees % 90 === 0) { - var d = degrees % 360; - if (d < 0) { - d += 360; - } + var d = $.positiveModulo(degrees, 360); switch (d) { case 0: cos = 1; diff --git a/src/rectangle.js b/src/rectangle.js index 98c839de..9d284df0 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -81,10 +81,7 @@ $.Rect = function(x, y, width, height, degrees) { this.degrees = typeof(degrees) === "number" ? degrees : 0; // Normalizes the rectangle. - this.degrees = this.degrees % 360; - if (this.degrees < 0) { - this.degrees += 360; - } + this.degrees = $.positiveModulo(this.degrees, 360); var newTopLeft, newWidth; if (this.degrees >= 270) { newTopLeft = this.getTopRight(); @@ -442,13 +439,10 @@ $.Rect.prototype = { * @return {OpenSeadragon.Rect} */ rotate: function(degrees, pivot) { - degrees = degrees % 360; + degrees = $.positiveModulo(degrees, 360); if (degrees === 0) { return this.clone(); } - if (degrees < 0) { - degrees += 360; - } pivot = pivot || this.getCenter(); var newTopLeft = this.getTopLeft().rotate(degrees, pivot); diff --git a/src/tiledimage.js b/src/tiledimage.js index e4749fbc..ad65d3e7 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -159,8 +159,8 @@ $.TiledImage = function( options ) { crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy, placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle, opacity: $.DEFAULT_SETTINGS.opacity, - compositeOperation: $.DEFAULT_SETTINGS.compositeOperation - + compositeOperation: $.DEFAULT_SETTINGS.compositeOperation, + degrees: 0 }, options ); this._xSpring = new $.Spring({ @@ -274,13 +274,19 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {Boolean} [current=false] - Pass true for the current location; false for target location. */ getBounds: function(current) { - if (current) { - return new $.Rect( this._xSpring.current.value, this._ySpring.current.value, - this._worldWidthCurrent, this._worldHeightCurrent ); - } - - return new $.Rect( this._xSpring.target.value, this._ySpring.target.value, - this._worldWidthTarget, this._worldHeightTarget ); + return current ? + new $.Rect( + this._xSpring.current.value, + this._ySpring.current.value, + this._worldWidthCurrent, + this._worldHeightCurrent, + this.degrees) : + new $.Rect( + this._xSpring.target.value, + this._ySpring.target.value, + this._worldWidthTarget, + this._worldHeightTarget, + this.degrees); }, // deprecated @@ -304,7 +310,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag bounds.x + clip.x, bounds.y + clip.y, clip.width, - clip.height); + clip.height, + this.degrees); } return bounds; }, @@ -660,6 +667,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag $.console.assert(!newClip || newClip instanceof $.Rect, "[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null"); +//TODO: should this._raiseBoundsChange(); be called? + if (newClip instanceof $.Rect) { this._clip = newClip.clone(); } else { @@ -684,6 +693,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag this._needsDraw = true; }, + /** + * Get the current rotation of this tiled image in degrees. + * @returns {Number} the current rotation of this tiled image in degrees. + */ + getRotation: function() { + return this.degrees; + }, + + /** + * Set the current rotation of this tiled image in degrees. + * @param {Number} the rotation in degrees. + */ + setRotation: function(degrees) { + this.degrees = $.positiveModulo(degrees, 360); + this._needsDraw = true; + }, + /** * @returns {String} The TiledImage's current compositeOperation. */ @@ -803,7 +829,8 @@ function updateViewport( tiledImage ) { } if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) { - var tiledImageBounds = tiledImage.getClippedBounds(true); + var tiledImageBounds = tiledImage.getClippedBounds(true) + .getBoundingBox(); var intersection = viewportBounds.intersection(tiledImageBounds); if (intersection === null) { return; @@ -1464,10 +1491,20 @@ function drawTiles( tiledImage, lastDrawn ) { tiledImage._drawer._clear(true, bounds); } - // When scaling, we must rotate only when blending the sketch canvas to avoid - // interpolation - if (tiledImage.viewport.degrees !== 0 && !sketchScale) { - tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, useSketch); + // When scaling, we must rotate only when blending the sketch canvas to + // avoid interpolation + if (!sketchScale) { + if (tiledImage.viewport.degrees !== 0) { + tiledImage._drawer._offsetForRotation( + tiledImage.viewport.degrees, useSketch); + } + if (tiledImage.degrees !== 0) { + tiledImage._drawer._offsetForRotation( + tiledImage.degrees, + tiledImage.viewport.pixelFromPointNoRotate( + tiledImage.getBounds(true).getTopLeft(), true), + useSketch); + } } var usedClip = false; @@ -1535,14 +1572,28 @@ function drawTiles( tiledImage, lastDrawn ) { tiledImage._drawer.restoreContext( useSketch ); } - if (tiledImage.viewport.degrees !== 0 && !sketchScale) { - tiledImage._drawer._restoreRotationChanges(useSketch); + if (!sketchScale) { + if (tiledImage.degrees !== 0) { + tiledImage._drawer._restoreRotationChanges(useSketch); + } + if (tiledImage.viewport.degrees !== 0) { + tiledImage._drawer._restoreRotationChanges(useSketch); + } } if (useSketch) { - var offsetForRotation = tiledImage.viewport.degrees !== 0 && sketchScale; - if (offsetForRotation) { - tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, false); + if (sketchScale) { + if (tiledImage.viewport.degrees !== 0) { + tiledImage._drawer._offsetForRotation( + tiledImage.viewport.degrees, false); + } + if (tiledImage.degrees !== 0) { + tiledImage._drawer._offsetForRotation( + tiledImage.degrees, + tiledImage.viewport.pixelFromPointNoRotate( + tiledImage.getBounds(true).getTopLeft(), true), + useSketch); + } } tiledImage._drawer.blendSketch({ opacity: tiledImage.opacity, @@ -1551,8 +1602,13 @@ function drawTiles( tiledImage, lastDrawn ) { compositeOperation: tiledImage.compositeOperation, bounds: bounds }); - if (offsetForRotation) { - tiledImage._drawer._restoreRotationChanges(false); + if (sketchScale) { + if (tiledImage.degrees !== 0) { + tiledImage._drawer._restoreRotationChanges(false); + } + if (tiledImage.viewport.degrees !== 0) { + tiledImage._drawer._restoreRotationChanges(false); + } } } drawDebugInfo( tiledImage, lastDrawn ); @@ -1563,7 +1619,8 @@ function drawDebugInfo( tiledImage, lastDrawn ) { for ( var i = lastDrawn.length - 1; i >= 0; i-- ) { var tile = lastDrawn[ i ]; try { - tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i ); + tiledImage._drawer.drawDebugInfo( + tile, lastDrawn.length, i, tiledImage); } catch(e) { $.console.error(e); } diff --git a/src/viewer.js b/src/viewer.js index 02556bcf..6ef8b4de 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1369,6 +1369,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, clip: queueItem.options.clip, placeholderFillStyle: queueItem.options.placeholderFillStyle, opacity: queueItem.options.opacity, + degrees: queueItem.options.degrees, compositeOperation: queueItem.options.compositeOperation, springStiffness: _this.springStiffness, animationTime: _this.animationTime, diff --git a/src/viewport.js b/src/viewport.js index 2ac499d4..4cac87c3 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -852,11 +852,7 @@ $.Viewport.prototype = { return this; } - degrees = degrees % 360; - if (degrees < 0) { - degrees += 360; - } - this.degrees = degrees; + this.degrees = $.positiveModulo(degrees, 360); this._setContentBounds( this.viewer.world.getHomeBounds(), this.viewer.world.getContentFactor());