Fix tiles missing with rotation + rotate around center

This commit is contained in:
Antoine Vandecreme 2016-08-28 12:10:35 +02:00
parent f0cb707ff2
commit 2e3f57401f
4 changed files with 121 additions and 77 deletions

View File

@ -483,7 +483,7 @@ $.Drawer.prototype = {
this._offsetForRotation( this._offsetForRotation(
tiledImage.getRotation(), tiledImage.getRotation(),
tiledImage.viewport.pixelFromPointNoRotate( tiledImage.viewport.pixelFromPointNoRotate(
tiledImage.getBounds(true).getTopLeft(), true)); tiledImage._getRotationPoint(true), true));
} }
context.strokeRect( context.strokeRect(

View File

@ -449,6 +449,11 @@ $.Rect.prototype = {
var newTopRight = this.getTopRight().rotate(degrees, pivot); var newTopRight = this.getTopRight().rotate(degrees, pivot);
var diff = newTopRight.minus(newTopLeft); var diff = newTopRight.minus(newTopLeft);
// Handle floating point error
diff = diff.apply(function(x) {
var EPSILON = 1e-15;
return Math.abs(x) < EPSILON ? 0 : x;
});
var radians = Math.atan(diff.y / diff.x); var radians = Math.atan(diff.y / diff.x);
if (diff.x < 0) { if (diff.x < 0) {
radians += Math.PI; radians += Math.PI;

View File

@ -305,23 +305,35 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
}, },
/** /**
* Get this TiledImage's bounds in viewport coordinates.
* @param {Boolean} [current=false] - Pass true for the current location;
* false for target location.
* @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates. * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
* @param {Boolean} [current=false] - Pass true for the current location; false for target location.
*/ */
getBounds: function(current) { getBounds: function(current) {
return this.getBoundsNoRotate(current)
.rotate(this._degrees, this._getRotationPoint(current));
},
/**
* Get this TiledImage's bounds in viewport coordinates without taking
* rotation into account.
* @param {Boolean} [current=false] - Pass true for the current location;
* false for target location.
* @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
*/
getBoundsNoRotate: function(current) {
return current ? return current ?
new $.Rect( new $.Rect(
this._xSpring.current.value, this._xSpring.current.value,
this._ySpring.current.value, this._ySpring.current.value,
this._worldWidthCurrent, this._worldWidthCurrent,
this._worldHeightCurrent, this._worldHeightCurrent) :
this._degrees) :
new $.Rect( new $.Rect(
this._xSpring.target.value, this._xSpring.target.value,
this._ySpring.target.value, this._ySpring.target.value,
this._worldWidthTarget, this._worldWidthTarget,
this._worldHeightTarget, this._worldHeightTarget);
this._degrees);
}, },
// deprecated // deprecated
@ -337,18 +349,19 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @returns {$.Rect} The clipped bounds in viewport coordinates. * @returns {$.Rect} The clipped bounds in viewport coordinates.
*/ */
getClippedBounds: function(current) { getClippedBounds: function(current) {
var bounds = this.getBounds(current); var bounds = this.getBoundsNoRotate(current);
if (this._clip) { if (this._clip) {
var ratio = this._worldWidthCurrent / this.source.dimensions.x; var worldWidth = current ?
this._worldWidthCurrent : this._worldWidthTarget;
var ratio = worldWidth / this.source.dimensions.x;
var clip = this._clip.times(ratio); var clip = this._clip.times(ratio);
bounds = new $.Rect( bounds = new $.Rect(
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.rotate(this._degrees, this._getRotationPoint(current));
}, },
/** /**
@ -373,21 +386,24 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @param {Boolean} [current=false] - Pass true to use the current location; false for target location. * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
* @return {OpenSeadragon.Point} A point representing the coordinates in the image. * @return {OpenSeadragon.Point} A point representing the coordinates in the image.
*/ */
viewportToImageCoordinates: function( viewerX, viewerY, current ) { viewportToImageCoordinates: function(viewerX, viewerY, current) {
var point;
if (viewerX instanceof $.Point) { if (viewerX instanceof $.Point) {
//they passed a point instead of individual components //they passed a point instead of individual components
current = viewerY; current = viewerY;
viewerY = viewerX.y; point = viewerX;
viewerX = viewerX.x; } else {
point = new $.Point(viewerX, viewerY);
} }
if (current) { point = point.rotate(-this._degrees, this._getRotationPoint(current));
return this._viewportToImageDelta(viewerX - this._xSpring.current.value, return current ?
viewerY - this._ySpring.current.value); this._viewportToImageDelta(
} point.x - this._xSpring.current.value,
point.y - this._ySpring.current.value) :
return this._viewportToImageDelta(viewerX - this._xSpring.target.value, this._viewportToImageDelta(
viewerY - this._ySpring.target.value); point.x - this._xSpring.target.value,
point.y - this._ySpring.target.value);
}, },
// private // private
@ -405,7 +421,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @param {Boolean} [current=false] - Pass true to use the current location; false for target location. * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
* @return {OpenSeadragon.Point} A point representing the coordinates in the viewport. * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport.
*/ */
imageToViewportCoordinates: function( imageX, imageY, current ) { imageToViewportCoordinates: function(imageX, imageY, current) {
if (imageX instanceof $.Point) { if (imageX instanceof $.Point) {
//they passed a point instead of individual components //they passed a point instead of individual components
current = imageY; current = imageY;
@ -422,7 +438,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
point.y += this._ySpring.target.value; point.y += this._ySpring.target.value;
} }
return point; return point.rotate(this._degrees, this._getRotationPoint(current));
}, },
/** /**
@ -453,7 +469,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
coordA.y, coordA.y,
coordB.x, coordB.x,
coordB.y, coordB.y,
rect.degrees rect.degrees + this._degrees
); );
}, },
@ -485,7 +501,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
coordA.y, coordA.y,
coordB.x, coordB.x,
coordB.y, coordB.y,
rect.degrees rect.degrees - this._degrees
); );
}, },
@ -533,6 +549,32 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
OpenSeadragon.getElementPosition( this.viewer.element )); OpenSeadragon.getElementPosition( this.viewer.element ));
}, },
// private
// Convert rectangle in tiled image coordinates to viewport coordinates.
_tiledImageToViewportRectangle: function(rect) {
var scale = this._scaleSpring.current.value;
return new $.Rect(
rect.x * scale + this._xSpring.current.value,
rect.y * scale + this._ySpring.current.value,
rect.width * scale,
rect.height * scale,
rect.degrees)
.rotate(this.getRotation(), this._getRotationPoint(true));
},
// private
// Convert rectangle in viewport coordinates to tiled image coordinates.
_viewportToTiledImageRectangle: function(rect) {
var scale = this._scaleSpring.current.value;
rect = rect.rotate(-this.getRotation(), this._getRotationPoint(true));
return new $.Rect(
(rect.x - this._xSpring.current.value) / scale,
(rect.y - this._ySpring.current.value) / scale,
rect.width / scale,
rect.height / scale,
rect.degrees);
},
/** /**
* Convert a viewport zoom to an image zoom. * Convert a viewport zoom to an image zoom.
* Image zoom: ratio of the original image size to displayed image size. * Image zoom: ratio of the original image size to displayed image size.
@ -738,7 +780,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
/** /**
* Set the current rotation of this tiled image in degrees. * Set the current rotation of this tiled image in degrees.
* @param {Number} the rotation in degrees. * @param {Number} degrees the rotation in degrees.
*/ */
setRotation: function(degrees) { setRotation: function(degrees) {
degrees = $.positiveModulo(degrees, 360); degrees = $.positiveModulo(degrees, 360);
@ -750,6 +792,16 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._raiseBoundsChange(); this._raiseBoundsChange();
}, },
/**
* @private
* Get the point around which this tiled image is rotated
* @param {Boolean} current True for current rotation point, false for target.
* @returns {OpenSeadragon.Point}
*/
_getRotationPoint: function(current) {
return this.getBoundsNoRotate(current).getTopLeft();
},
/** /**
* @returns {String} The TiledImage's current compositeOperation. * @returns {String} The TiledImage's current compositeOperation.
*/ */
@ -848,51 +900,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
_updateViewport: function() { _updateViewport: function() {
this._needsDraw = false; this._needsDraw = false;
var viewport = this.viewport;
var viewportBounds = viewport.getBoundsWithMargins(true);
// Reset tile's internal drawn state // Reset tile's internal drawn state
while (this.lastDrawn.length > 0) { while (this.lastDrawn.length > 0) {
var tile = this.lastDrawn.pop(); var tile = this.lastDrawn.pop();
tile.beingDrawn = false; tile.beingDrawn = false;
} }
var viewport = this.viewport;
var drawArea = this._viewportToTiledImageRectangle(
viewport.getBoundsWithMargins(true));
if (!this.wrapHorizontal && !this.wrapVertical) { if (!this.wrapHorizontal && !this.wrapVertical) {
var tiledImageBounds = this.getClippedBounds(true) var tiledImageBounds = this._viewportToTiledImageRectangle(
.getBoundingBox(); this.getClippedBounds(true));
var intersection = viewportBounds.intersection(tiledImageBounds); drawArea = drawArea.intersection(tiledImageBounds);
if (intersection === null) { if (drawArea === null) {
return; return;
} }
viewportBounds = intersection;
}
viewportBounds = viewportBounds.getBoundingBox();
viewportBounds.x -= this._xSpring.current.value;
viewportBounds.y -= this._ySpring.current.value;
var viewportTL = viewportBounds.getTopLeft();
var viewportBR = viewportBounds.getBottomRight();
//Don't draw if completely outside of the viewport
if (!this.wrapHorizontal &&
(viewportBR.x < 0 || viewportTL.x > this._worldWidthCurrent)) {
return;
}
if (!this.wrapVertical &&
(viewportBR.y < 0 || viewportTL.y > this._worldHeightCurrent)) {
return;
}
// Calculate viewport rect / bounds
if (!this.wrapHorizontal) {
viewportTL.x = Math.max(viewportTL.x, 0);
viewportBR.x = Math.min(viewportBR.x, this._worldWidthCurrent );
}
if (!this.wrapVertical) {
viewportTL.y = Math.max(viewportTL.y, 0);
viewportBR.y = Math.min(viewportBR.y, this._worldHeightCurrent);
} }
var levelsInterval = this._getLevelsInterval(); var levelsInterval = this._getLevelsInterval();
@ -950,8 +974,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
level, level,
levelOpacity, levelOpacity,
levelVisibility, levelVisibility,
viewportTL, drawArea,
viewportBR,
currentTime, currentTime,
bestTile bestTile
); );
@ -976,7 +999,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
} }
}); });
function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){ function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
levelVisibility, drawArea, currentTime, best) {
var topLeftBound = drawArea.getBoundingBox().getTopLeft();
var bottomRightBound = drawArea.getBoundingBox().getBottomRight();
if (tiledImage.viewer) { if (tiledImage.viewer) {
/** /**
@ -991,8 +1018,9 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
* @property {Object} level * @property {Object} level
* @property {Object} opacity * @property {Object} opacity
* @property {Object} visibility * @property {Object} visibility
* @property {Object} topleft * @property {OpenSeadragon.Rect} drawArea
* @property {Object} bottomright * @property {Object} topleft deprecated, use drawArea instead
* @property {Object} bottomright deprecated, use drawArea instead
* @property {Object} currenttime * @property {Object} currenttime
* @property {Object} best * @property {Object} best
* @property {?Object} userData - Arbitrary subscriber-defined object. * @property {?Object} userData - Arbitrary subscriber-defined object.
@ -1003,18 +1031,17 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
level: level, level: level,
opacity: levelOpacity, opacity: levelOpacity,
visibility: levelVisibility, visibility: levelVisibility,
topleft: viewportTL, drawArea: drawArea,
bottomright: viewportBR, topleft: topLeftBound,
bottomright: bottomRightBound,
currenttime: currentTime, currenttime: currentTime,
best: best best: best
}); });
} }
//OK, a new drawing so do your calculations //OK, a new drawing so do your calculations
var topLeftTile = tiledImage.source.getTileAtPoint( var topLeftTile = tiledImage.source.getTileAtPoint(level, topLeftBound);
level, viewportTL.divide(tiledImage._scaleSpring.current.value)); var bottomRightTile = tiledImage.source.getTileAtPoint(level, bottomRightBound);
var bottomRightTile = tiledImage.source.getTileAtPoint(
level, viewportBR.divide(tiledImage._scaleSpring.current.value));
var numberOfTiles = tiledImage.source.getNumTiles(level); var numberOfTiles = tiledImage.source.getNumTiles(level);
resetCoverage(tiledImage.coverage, level); resetCoverage(tiledImage.coverage, level);
@ -1022,11 +1049,15 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
if (tiledImage.wrapHorizontal) { if (tiledImage.wrapHorizontal) {
topLeftTile.x -= 1; // left invisible column (othervise we will have empty space after scroll at left) topLeftTile.x -= 1; // left invisible column (othervise we will have empty space after scroll at left)
} else { } else {
// Adjust for floating point error
topLeftTile.x = Math.max(topLeftTile.x, 0);
bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1); bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1);
} }
if (tiledImage.wrapVertical) { if (tiledImage.wrapVertical) {
topLeftTile.y -= 1; // top invisible row (othervise we will have empty space after scroll at top) topLeftTile.y -= 1; // top invisible row (othervise we will have empty space after scroll at top)
} else { } else {
// Adjust for floating point error
topLeftTile.y = Math.max(topLeftTile.y, 0);
bottomRightTile.y = Math.min(bottomRightTile.y, numberOfTiles.y - 1); bottomRightTile.y = Math.min(bottomRightTile.y, numberOfTiles.y - 1);
} }
@ -1035,6 +1066,13 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) { for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) { for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
var tileBounds = tiledImage.source.getTileBounds(level, x, y);
if (drawArea.intersection(tileBounds) === null) {
// This tile is outside of the viewport, no need to draw it
continue;
}
best = updateTile( best = updateTile(
tiledImage, tiledImage,
drawLevel, drawLevel,
@ -1529,7 +1567,7 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer._offsetForRotation( tiledImage._drawer._offsetForRotation(
tiledImage._degrees, tiledImage._degrees,
tiledImage.viewport.pixelFromPointNoRotate( tiledImage.viewport.pixelFromPointNoRotate(
tiledImage.getBounds(true).getTopLeft(), true), tiledImage._getRotationPoint(true), true),
useSketch); useSketch);
} }
} }
@ -1539,6 +1577,7 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer.saveContext(useSketch); tiledImage._drawer.saveContext(useSketch);
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true); var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
box = box.rotate(-tiledImage._degrees, tiledImage._getRotationPoint());
var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box); var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
if (sketchScale) { if (sketchScale) {
clipRect = clipRect.times(sketchScale); clipRect = clipRect.times(sketchScale);
@ -1618,7 +1657,7 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer._offsetForRotation( tiledImage._drawer._offsetForRotation(
tiledImage._degrees, tiledImage._degrees,
tiledImage.viewport.pixelFromPointNoRotate( tiledImage.viewport.pixelFromPointNoRotate(
tiledImage.getBounds(true).getTopLeft(), true), tiledImage._getRotationPoint(true), true),
useSketch); useSketch);
} }
} }

View File

@ -383,7 +383,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
var item = this._items[0]; var item = this._items[0];
var bounds = item.getBounds(); var bounds = item.getBounds();
this._contentFactor = item.getContentSize().x / bounds.width; this._contentFactor = item.getContentSize().x / bounds.width;
var clippedBounds = item.getClippedBounds(); var clippedBounds = item.getClippedBounds().getBoundingBox();
var left = clippedBounds.x; var left = clippedBounds.x;
var top = clippedBounds.y; var top = clippedBounds.y;
var right = clippedBounds.x + clippedBounds.width; var right = clippedBounds.x + clippedBounds.width;
@ -393,7 +393,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
bounds = item.getBounds(); bounds = item.getBounds();
this._contentFactor = Math.max(this._contentFactor, this._contentFactor = Math.max(this._contentFactor,
item.getContentSize().x / bounds.width); item.getContentSize().x / bounds.width);
clippedBounds = item.getClippedBounds(); clippedBounds = item.getClippedBounds().getBoundingBox();
left = Math.min(left, clippedBounds.x); left = Math.min(left, clippedBounds.x);
top = Math.min(top, clippedBounds.y); top = Math.min(top, clippedBounds.y);
right = Math.max(right, clippedBounds.x + clippedBounds.width); right = Math.max(right, clippedBounds.x + clippedBounds.width);