First draft of tiled image rotation.

This commit is contained in:
Antoine Vandecreme 2016-08-17 15:43:08 +02:00
parent bbf354b6b6
commit 65b59c08d6
7 changed files with 118 additions and 47 deletions

View File

@ -464,7 +464,7 @@ $.Drawer.prototype = {
}, },
// private // private
drawDebugInfo: function( tile, count, i ){ drawDebugInfo: function(tile, count, i, tiledImage) {
if ( !this.useCanvas ) { if ( !this.useCanvas ) {
return; return;
} }
@ -479,6 +479,12 @@ $.Drawer.prototype = {
if ( this.viewport.degrees !== 0 ) { if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation(this.viewport.degrees); this._offsetForRotation(this.viewport.degrees);
} }
if (tiledImage.degrees) {
this._offsetForRotation(
tiledImage.degrees,
tiledImage.viewport.pixelFromPointNoRotate(
tiledImage.getBounds(true).getTopLeft(), true));
}
context.strokeRect( context.strokeRect(
tile.position.x * $.pixelDensityRatio, tile.position.x * $.pixelDensityRatio,
@ -541,6 +547,9 @@ $.Drawer.prototype = {
if ( this.viewport.degrees !== 0 ) { if ( this.viewport.degrees !== 0 ) {
this._restoreRotationChanges(); this._restoreRotationChanges();
} }
if (tiledImage.degrees) {
this._restoreRotationChanges();
}
context.restore(); context.restore();
}, },
@ -574,17 +583,19 @@ $.Drawer.prototype = {
return new $.Point(canvas.width, canvas.height); return new $.Point(canvas.width, canvas.height);
}, },
// private getCanvasCenter: function() {
_offsetForRotation: function(degrees, useSketch) { return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
var cx = this.canvas.width / 2; },
var cy = this.canvas.height / 2;
// private
_offsetForRotation: function(degrees, point, useSketch) {
point = point || this.getCanvasCenter();
var context = this._getContext(useSketch); var context = this._getContext(useSketch);
context.save(); context.save();
context.translate(cx, cy); context.translate(point.x, point.y);
context.rotate(Math.PI / 180 * degrees); context.rotate(Math.PI / 180 * degrees);
context.translate(-cx, -cy); context.translate(-point.x, -point.y);
}, },
// private // private

View File

@ -1375,6 +1375,21 @@ function OpenSeadragon( options ){
return string.charAt(0).toUpperCase() + string.slice(1); 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). * Determines if a point is within the bounding rectangle of the given element (hit-test).
* @function * @function

View File

@ -190,10 +190,7 @@ $.Point.prototype = {
var sin; var sin;
// Avoid float computations when possible // Avoid float computations when possible
if (degrees % 90 === 0) { if (degrees % 90 === 0) {
var d = degrees % 360; var d = $.positiveModulo(degrees, 360);
if (d < 0) {
d += 360;
}
switch (d) { switch (d) {
case 0: case 0:
cos = 1; cos = 1;

View File

@ -81,10 +81,7 @@ $.Rect = function(x, y, width, height, degrees) {
this.degrees = typeof(degrees) === "number" ? degrees : 0; this.degrees = typeof(degrees) === "number" ? degrees : 0;
// Normalizes the rectangle. // Normalizes the rectangle.
this.degrees = this.degrees % 360; this.degrees = $.positiveModulo(this.degrees, 360);
if (this.degrees < 0) {
this.degrees += 360;
}
var newTopLeft, newWidth; var newTopLeft, newWidth;
if (this.degrees >= 270) { if (this.degrees >= 270) {
newTopLeft = this.getTopRight(); newTopLeft = this.getTopRight();
@ -442,13 +439,10 @@ $.Rect.prototype = {
* @return {OpenSeadragon.Rect} * @return {OpenSeadragon.Rect}
*/ */
rotate: function(degrees, pivot) { rotate: function(degrees, pivot) {
degrees = degrees % 360; degrees = $.positiveModulo(degrees, 360);
if (degrees === 0) { if (degrees === 0) {
return this.clone(); return this.clone();
} }
if (degrees < 0) {
degrees += 360;
}
pivot = pivot || this.getCenter(); pivot = pivot || this.getCenter();
var newTopLeft = this.getTopLeft().rotate(degrees, pivot); var newTopLeft = this.getTopLeft().rotate(degrees, pivot);

View File

@ -159,8 +159,8 @@ $.TiledImage = function( options ) {
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy, crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle, placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity, opacity: $.DEFAULT_SETTINGS.opacity,
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation compositeOperation: $.DEFAULT_SETTINGS.compositeOperation,
degrees: 0
}, options ); }, options );
this._xSpring = new $.Spring({ 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. * @param {Boolean} [current=false] - Pass true for the current location; false for target location.
*/ */
getBounds: function(current) { getBounds: function(current) {
if (current) { return current ?
return new $.Rect( this._xSpring.current.value, this._ySpring.current.value, new $.Rect(
this._worldWidthCurrent, this._worldHeightCurrent ); this._xSpring.current.value,
} this._ySpring.current.value,
this._worldWidthCurrent,
return new $.Rect( this._xSpring.target.value, this._ySpring.target.value, this._worldHeightCurrent,
this._worldWidthTarget, this._worldHeightTarget ); this.degrees) :
new $.Rect(
this._xSpring.target.value,
this._ySpring.target.value,
this._worldWidthTarget,
this._worldHeightTarget,
this.degrees);
}, },
// deprecated // deprecated
@ -304,7 +310,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
bounds.x + clip.x, bounds.x + clip.x,
bounds.y + clip.y, bounds.y + clip.y,
clip.width, clip.width,
clip.height); clip.height,
this.degrees);
} }
return bounds; return bounds;
}, },
@ -660,6 +667,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
$.console.assert(!newClip || newClip instanceof $.Rect, $.console.assert(!newClip || newClip instanceof $.Rect,
"[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null"); "[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null");
//TODO: should this._raiseBoundsChange(); be called?
if (newClip instanceof $.Rect) { if (newClip instanceof $.Rect) {
this._clip = newClip.clone(); this._clip = newClip.clone();
} else { } else {
@ -684,6 +693,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._needsDraw = true; 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. * @returns {String} The TiledImage's current compositeOperation.
*/ */
@ -803,7 +829,8 @@ function updateViewport( tiledImage ) {
} }
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) { if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
var tiledImageBounds = tiledImage.getClippedBounds(true); var tiledImageBounds = tiledImage.getClippedBounds(true)
.getBoundingBox();
var intersection = viewportBounds.intersection(tiledImageBounds); var intersection = viewportBounds.intersection(tiledImageBounds);
if (intersection === null) { if (intersection === null) {
return; return;
@ -1464,10 +1491,20 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer._clear(true, bounds); tiledImage._drawer._clear(true, bounds);
} }
// When scaling, we must rotate only when blending the sketch canvas to avoid // When scaling, we must rotate only when blending the sketch canvas to
// interpolation // avoid interpolation
if (tiledImage.viewport.degrees !== 0 && !sketchScale) { if (!sketchScale) {
tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, useSketch); 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; var usedClip = false;
@ -1535,14 +1572,28 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer.restoreContext( useSketch ); tiledImage._drawer.restoreContext( useSketch );
} }
if (tiledImage.viewport.degrees !== 0 && !sketchScale) { if (!sketchScale) {
tiledImage._drawer._restoreRotationChanges(useSketch); if (tiledImage.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(useSketch);
}
if (tiledImage.viewport.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(useSketch);
}
} }
if (useSketch) { if (useSketch) {
var offsetForRotation = tiledImage.viewport.degrees !== 0 && sketchScale; if (sketchScale) {
if (offsetForRotation) { if (tiledImage.viewport.degrees !== 0) {
tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, false); 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({ tiledImage._drawer.blendSketch({
opacity: tiledImage.opacity, opacity: tiledImage.opacity,
@ -1551,8 +1602,13 @@ function drawTiles( tiledImage, lastDrawn ) {
compositeOperation: tiledImage.compositeOperation, compositeOperation: tiledImage.compositeOperation,
bounds: bounds bounds: bounds
}); });
if (offsetForRotation) { if (sketchScale) {
tiledImage._drawer._restoreRotationChanges(false); if (tiledImage.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(false);
}
if (tiledImage.viewport.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(false);
}
} }
} }
drawDebugInfo( tiledImage, lastDrawn ); drawDebugInfo( tiledImage, lastDrawn );
@ -1563,7 +1619,8 @@ function drawDebugInfo( tiledImage, lastDrawn ) {
for ( var i = lastDrawn.length - 1; i >= 0; i-- ) { for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
var tile = lastDrawn[ i ]; var tile = lastDrawn[ i ];
try { try {
tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i ); tiledImage._drawer.drawDebugInfo(
tile, lastDrawn.length, i, tiledImage);
} catch(e) { } catch(e) {
$.console.error(e); $.console.error(e);
} }

View File

@ -1369,6 +1369,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
clip: queueItem.options.clip, clip: queueItem.options.clip,
placeholderFillStyle: queueItem.options.placeholderFillStyle, placeholderFillStyle: queueItem.options.placeholderFillStyle,
opacity: queueItem.options.opacity, opacity: queueItem.options.opacity,
degrees: queueItem.options.degrees,
compositeOperation: queueItem.options.compositeOperation, compositeOperation: queueItem.options.compositeOperation,
springStiffness: _this.springStiffness, springStiffness: _this.springStiffness,
animationTime: _this.animationTime, animationTime: _this.animationTime,

View File

@ -852,11 +852,7 @@ $.Viewport.prototype = {
return this; return this;
} }
degrees = degrees % 360; this.degrees = $.positiveModulo(degrees, 360);
if (degrees < 0) {
degrees += 360;
}
this.degrees = degrees;
this._setContentBounds( this._setContentBounds(
this.viewer.world.getHomeBounds(), this.viewer.world.getHomeBounds(),
this.viewer.world.getContentFactor()); this.viewer.world.getContentFactor());