mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-22 05:06:09 +03:00
Merge pull request #939 from avandecreme/perf
Avoid loading clipped out tiles. Fix #889.
This commit is contained in:
commit
bd743d688a
163
src/rectangle.js
163
src/rectangle.js
@ -307,6 +307,132 @@ $.Rect.prototype = {
|
|||||||
bottom - top);
|
bottom - top);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the bounding box of the intersection of this rectangle with the
|
||||||
|
* given rectangle.
|
||||||
|
* @param {OpenSeadragon.Rect} rect
|
||||||
|
* @return {OpenSeadragon.Rect} the bounding box of the intersection
|
||||||
|
* or null if the rectangles don't intersect.
|
||||||
|
*/
|
||||||
|
intersection: function(rect) {
|
||||||
|
// Simplified version of Weiler Atherton clipping algorithm
|
||||||
|
// https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm
|
||||||
|
// Because we just want the bounding box of the intersection,
|
||||||
|
// we can just compute the bounding box of:
|
||||||
|
// 1. all the summits of this which are inside rect
|
||||||
|
// 2. all the summits of rect which are inside this
|
||||||
|
// 3. all the intersections of rect and this
|
||||||
|
var EPSILON = 0.0000000001;
|
||||||
|
|
||||||
|
var intersectionPoints = [];
|
||||||
|
|
||||||
|
var thisTopLeft = this.getTopLeft();
|
||||||
|
if (rect.containsPoint(thisTopLeft, EPSILON)) {
|
||||||
|
intersectionPoints.push(thisTopLeft);
|
||||||
|
}
|
||||||
|
var thisTopRight = this.getTopRight();
|
||||||
|
if (rect.containsPoint(thisTopRight, EPSILON)) {
|
||||||
|
intersectionPoints.push(thisTopRight);
|
||||||
|
}
|
||||||
|
var thisBottomLeft = this.getBottomLeft();
|
||||||
|
if (rect.containsPoint(thisBottomLeft, EPSILON)) {
|
||||||
|
intersectionPoints.push(thisBottomLeft);
|
||||||
|
}
|
||||||
|
var thisBottomRight = this.getBottomRight();
|
||||||
|
if (rect.containsPoint(thisBottomRight, EPSILON)) {
|
||||||
|
intersectionPoints.push(thisBottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rectTopLeft = rect.getTopLeft();
|
||||||
|
if (this.containsPoint(rectTopLeft, EPSILON)) {
|
||||||
|
intersectionPoints.push(rectTopLeft);
|
||||||
|
}
|
||||||
|
var rectTopRight = rect.getTopRight();
|
||||||
|
if (this.containsPoint(rectTopRight, EPSILON)) {
|
||||||
|
intersectionPoints.push(rectTopRight);
|
||||||
|
}
|
||||||
|
var rectBottomLeft = rect.getBottomLeft();
|
||||||
|
if (this.containsPoint(rectBottomLeft, EPSILON)) {
|
||||||
|
intersectionPoints.push(rectBottomLeft);
|
||||||
|
}
|
||||||
|
var rectBottomRight = rect.getBottomRight();
|
||||||
|
if (this.containsPoint(rectBottomRight, EPSILON)) {
|
||||||
|
intersectionPoints.push(rectBottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
var thisSegments = this._getSegments();
|
||||||
|
var rectSegments = rect._getSegments();
|
||||||
|
for (var i = 0; i < thisSegments.length; i++) {
|
||||||
|
var thisSegment = thisSegments[i];
|
||||||
|
for (var j = 0; j < rectSegments.length; j++) {
|
||||||
|
var rectSegment = rectSegments[j];
|
||||||
|
var intersect = getIntersection(thisSegment[0], thisSegment[1],
|
||||||
|
rectSegment[0], rectSegment[1]);
|
||||||
|
if (intersect) {
|
||||||
|
intersectionPoints.push(intersect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get intersection point of segments [a,b] and [c,d]
|
||||||
|
function getIntersection(a, b, c, d) {
|
||||||
|
// http://stackoverflow.com/a/1968345/1440403
|
||||||
|
var abVector = b.minus(a);
|
||||||
|
var cdVector = d.minus(c);
|
||||||
|
|
||||||
|
var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y;
|
||||||
|
if (denom === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom;
|
||||||
|
var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom;
|
||||||
|
|
||||||
|
if (-EPSILON <= s && s <= 1 - EPSILON &&
|
||||||
|
-EPSILON <= t && t <= 1 - EPSILON) {
|
||||||
|
return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersectionPoints.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minX = intersectionPoints[0].x;
|
||||||
|
var maxX = intersectionPoints[0].x;
|
||||||
|
var minY = intersectionPoints[0].y;
|
||||||
|
var maxY = intersectionPoints[0].y;
|
||||||
|
for (var k = 1; k < intersectionPoints.length; k++) {
|
||||||
|
var point = intersectionPoints[k];
|
||||||
|
if (point.x < minX) {
|
||||||
|
minX = point.x;
|
||||||
|
}
|
||||||
|
if (point.x > maxX) {
|
||||||
|
maxX = point.x;
|
||||||
|
}
|
||||||
|
if (point.y < minY) {
|
||||||
|
minY = point.y;
|
||||||
|
}
|
||||||
|
if (point.y > maxY) {
|
||||||
|
maxY = point.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new $.Rect(minX, minY, maxX - minX, maxY - minY);
|
||||||
|
},
|
||||||
|
|
||||||
|
// private
|
||||||
|
_getSegments: function() {
|
||||||
|
var topLeft = this.getTopLeft();
|
||||||
|
var topRight = this.getTopRight();
|
||||||
|
var bottomLeft = this.getBottomLeft();
|
||||||
|
var bottomRight = this.getBottomRight();
|
||||||
|
return [[topLeft, topRight],
|
||||||
|
[topRight, bottomRight],
|
||||||
|
[bottomRight, bottomLeft],
|
||||||
|
[bottomLeft, topLeft]];
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotates a rectangle around a point.
|
* Rotates a rectangle around a point.
|
||||||
* @function
|
* @function
|
||||||
@ -381,6 +507,37 @@ $.Rect.prototype = {
|
|||||||
return new $.Rect(x, y, width, height);
|
return new $.Rect(x, y, width, height);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a point is inside this rectangle (edge included).
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Point} point
|
||||||
|
* @param {Number} [epsilon=0] the margin of error allowed
|
||||||
|
* @returns {Boolean} true if the point is inside this rectangle, false
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
containsPoint: function(point, epsilon) {
|
||||||
|
epsilon = epsilon || 0;
|
||||||
|
|
||||||
|
// See http://stackoverflow.com/a/2752754/1440403 for explanation
|
||||||
|
var topLeft = this.getTopLeft();
|
||||||
|
var topRight = this.getTopRight();
|
||||||
|
var bottomLeft = this.getBottomLeft();
|
||||||
|
var topDiff = topRight.minus(topLeft);
|
||||||
|
var leftDiff = bottomLeft.minus(topLeft);
|
||||||
|
|
||||||
|
return ((point.x - topLeft.x) * topDiff.x +
|
||||||
|
(point.y - topLeft.y) * topDiff.y >= -epsilon) &&
|
||||||
|
|
||||||
|
((point.x - topRight.x) * topDiff.x +
|
||||||
|
(point.y - topRight.y) * topDiff.y <= epsilon) &&
|
||||||
|
|
||||||
|
((point.x - topLeft.x) * leftDiff.x +
|
||||||
|
(point.y - topLeft.y) * leftDiff.y >= -epsilon) &&
|
||||||
|
|
||||||
|
((point.x - bottomLeft.x) * leftDiff.x +
|
||||||
|
(point.y - bottomLeft.y) * leftDiff.y <= epsilon);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a string representation of the rectangle which is useful for
|
* Provides a string representation of the rectangle which is useful for
|
||||||
* debugging.
|
* debugging.
|
||||||
@ -389,10 +546,10 @@ $.Rect.prototype = {
|
|||||||
*/
|
*/
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return "[" +
|
return "[" +
|
||||||
(Math.round(this.x * 100) / 100) + "," +
|
(Math.round(this.x * 100) / 100) + ", " +
|
||||||
(Math.round(this.y * 100) / 100) + "," +
|
(Math.round(this.y * 100) / 100) + ", " +
|
||||||
(Math.round(this.width * 100) / 100) + "x" +
|
(Math.round(this.width * 100) / 100) + "x" +
|
||||||
(Math.round(this.height * 100) / 100) + "," +
|
(Math.round(this.height * 100) / 100) + ", " +
|
||||||
(Math.round(this.degrees * 100) / 100) + "deg" +
|
(Math.round(this.degrees * 100) / 100) + "deg" +
|
||||||
"]";
|
"]";
|
||||||
}
|
}
|
||||||
|
@ -787,7 +787,6 @@ function updateViewport( tiledImage ) {
|
|||||||
Math.log( 2 )
|
Math.log( 2 )
|
||||||
))
|
))
|
||||||
),
|
),
|
||||||
degrees = tiledImage.viewport.degrees,
|
|
||||||
renderPixelRatioC,
|
renderPixelRatioC,
|
||||||
renderPixelRatioT,
|
renderPixelRatioT,
|
||||||
zeroRatioT,
|
zeroRatioT,
|
||||||
@ -795,16 +794,24 @@ function updateViewport( tiledImage ) {
|
|||||||
levelOpacity,
|
levelOpacity,
|
||||||
levelVisibility;
|
levelVisibility;
|
||||||
|
|
||||||
viewportBounds = viewportBounds.getBoundingBox();
|
|
||||||
viewportBounds.x -= tiledImage._xSpring.current.value;
|
|
||||||
viewportBounds.y -= tiledImage._ySpring.current.value;
|
|
||||||
|
|
||||||
// Reset tile's internal drawn state
|
// Reset tile's internal drawn state
|
||||||
while ( tiledImage.lastDrawn.length > 0 ) {
|
while (tiledImage.lastDrawn.length > 0) {
|
||||||
tile = tiledImage.lastDrawn.pop();
|
tile = tiledImage.lastDrawn.pop();
|
||||||
tile.beingDrawn = false;
|
tile.beingDrawn = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
|
||||||
|
var tiledImageBounds = tiledImage.getClippedBounds(true);
|
||||||
|
var intersection = viewportBounds.intersection(tiledImageBounds);
|
||||||
|
if (intersection === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewportBounds = intersection;
|
||||||
|
}
|
||||||
|
viewportBounds = viewportBounds.getBoundingBox();
|
||||||
|
viewportBounds.x -= tiledImage._xSpring.current.value;
|
||||||
|
viewportBounds.y -= tiledImage._ySpring.current.value;
|
||||||
|
|
||||||
var viewportTL = viewportBounds.getTopLeft();
|
var viewportTL = viewportBounds.getTopLeft();
|
||||||
var viewportBR = viewportBounds.getBottomRight();
|
var viewportBR = viewportBounds.getBottomRight();
|
||||||
|
|
||||||
|
@ -664,15 +664,15 @@ $.Viewport.prototype = {
|
|||||||
var oldBounds = this.getBounds();
|
var oldBounds = this.getBounds();
|
||||||
var oldZoom = this.getZoom();
|
var oldZoom = this.getZoom();
|
||||||
|
|
||||||
if (Math.abs(newZoom - oldZoom) < 0.00000001 ||
|
if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {
|
||||||
Math.abs(newBounds.width - oldBounds.width) < 0.00000001) {
|
this.zoomTo(newZoom, true);
|
||||||
return this.panTo(center, immediately);
|
return this.panTo(center, immediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
newBounds = newBounds.rotate(-this.getRotation());
|
newBounds = newBounds.rotate(-this.getRotation());
|
||||||
var referencePoint = newBounds.getTopLeft().divide(newBounds.width)
|
var referencePoint = newBounds.getTopLeft().times(newZoom)
|
||||||
.minus(oldBounds.getTopLeft().divide(oldBounds.width))
|
.minus(oldBounds.getTopLeft().times(oldZoom))
|
||||||
.divide(1 / newBounds.width - 1 / oldBounds.width);
|
.divide(newZoom - oldZoom);
|
||||||
|
|
||||||
return this.zoomTo(newZoom, referencePoint, immediately);
|
return this.zoomTo(newZoom, referencePoint, immediately);
|
||||||
},
|
},
|
||||||
|
@ -161,6 +161,65 @@
|
|||||||
"Incorrect union with non horizontal rectangles.");
|
"Incorrect union with non horizontal rectangles.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('intersection', function() {
|
||||||
|
var rect1 = new OpenSeadragon.Rect(2, 2, 2, 3);
|
||||||
|
var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
|
||||||
|
var expected = null;
|
||||||
|
var actual = rect1.intersection(rect2);
|
||||||
|
equal(expected, actual,
|
||||||
|
"Rectangle " + rect2 + " should not intersect " + rect1);
|
||||||
|
actual = rect2.intersection(rect1);
|
||||||
|
equal(expected, actual,
|
||||||
|
"Rectangle " + rect1 + " should not intersect " + rect2);
|
||||||
|
|
||||||
|
rect1 = new OpenSeadragon.Rect(0, 0, 2, 1);
|
||||||
|
rect2 = new OpenSeadragon.Rect(1, 0, 2, 2);
|
||||||
|
expected = new OpenSeadragon.Rect(1, 0, 1, 1);
|
||||||
|
actual = rect1.intersection(rect2);
|
||||||
|
Util.assertRectangleEquals(expected, actual, precision,
|
||||||
|
"Intersection of " + rect2 + " with " + rect1 + " should be " +
|
||||||
|
expected);
|
||||||
|
actual = rect2.intersection(rect1);
|
||||||
|
Util.assertRectangleEquals(expected, actual, precision,
|
||||||
|
"Intersection of " + rect1 + " with " + rect2 + " should be " +
|
||||||
|
expected);
|
||||||
|
|
||||||
|
rect1 = new OpenSeadragon.Rect(0, 0, 3, 3);
|
||||||
|
rect2 = new OpenSeadragon.Rect(1, 1, 1, 1);
|
||||||
|
expected = new OpenSeadragon.Rect(1, 1, 1, 1);
|
||||||
|
actual = rect1.intersection(rect2);
|
||||||
|
Util.assertRectangleEquals(expected, actual, precision,
|
||||||
|
"Intersection of " + rect2 + " with " + rect1 + " should be " +
|
||||||
|
expected);
|
||||||
|
actual = rect2.intersection(rect1);
|
||||||
|
Util.assertRectangleEquals(expected, actual, precision,
|
||||||
|
"Intersection of " + rect1 + " with " + rect2 + " should be " +
|
||||||
|
expected);
|
||||||
|
|
||||||
|
|
||||||
|
rect1 = new OpenSeadragon.Rect(2, 2, 2, 3, 45);
|
||||||
|
rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
|
||||||
|
expected = null;
|
||||||
|
actual = rect1.intersection(rect2);
|
||||||
|
equal(expected, actual,
|
||||||
|
"Rectangle " + rect2 + " should not intersect " + rect1);
|
||||||
|
actual = rect2.intersection(rect1);
|
||||||
|
equal(expected, actual,
|
||||||
|
"Rectangle " + rect1 + " should not intersect " + rect2);
|
||||||
|
|
||||||
|
rect1 = new OpenSeadragon.Rect(2, 0, 2, 3, 45);
|
||||||
|
rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
|
||||||
|
expected = new OpenSeadragon.Rect(0, 1, 1, 1);
|
||||||
|
actual = rect1.intersection(rect2);
|
||||||
|
Util.assertRectangleEquals(expected, actual, precision,
|
||||||
|
"Intersection of " + rect2 + " with " + rect1 + " should be " +
|
||||||
|
expected);
|
||||||
|
actual = rect2.intersection(rect1);
|
||||||
|
Util.assertRectangleEquals(expected, actual, precision,
|
||||||
|
"Intersection of " + rect1 + " with " + rect2 + " should be " +
|
||||||
|
expected);
|
||||||
|
});
|
||||||
|
|
||||||
test('rotate', function() {
|
test('rotate', function() {
|
||||||
var rect = new OpenSeadragon.Rect(0, 0, 2, 1);
|
var rect = new OpenSeadragon.Rect(0, 0, 2, 1);
|
||||||
|
|
||||||
@ -218,4 +277,27 @@
|
|||||||
"Bounding box of rect rotated 270deg.");
|
"Bounding box of rect rotated 270deg.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('containsPoint', function() {
|
||||||
|
var rect = new OpenSeadragon.Rect(0, 0, 1, 1, 45);
|
||||||
|
|
||||||
|
ok(rect.containsPoint(new OpenSeadragon.Point(0, 0)),
|
||||||
|
'Point 0,0 should be inside ' + rect);
|
||||||
|
ok(rect.containsPoint(rect.getTopRight()),
|
||||||
|
'Top right vertex should be inside ' + rect);
|
||||||
|
ok(rect.containsPoint(rect.getBottomRight()),
|
||||||
|
'Bottom right vertex should be inside ' + rect);
|
||||||
|
ok(rect.containsPoint(rect.getBottomLeft()),
|
||||||
|
'Bottom left vertex should be inside ' + rect);
|
||||||
|
ok(rect.containsPoint(rect.getCenter()),
|
||||||
|
'Center should be inside ' + rect);
|
||||||
|
notOk(rect.containsPoint(new OpenSeadragon.Point(1, 0)),
|
||||||
|
'Point 1,0 should not be inside ' + rect);
|
||||||
|
ok(rect.containsPoint(new OpenSeadragon.Point(0.5, 0.5)),
|
||||||
|
'Point 0.5,0.5 should be inside ' + rect);
|
||||||
|
ok(rect.containsPoint(new OpenSeadragon.Point(0.4, 0.5)),
|
||||||
|
'Point 0.4,0.5 should be inside ' + rect);
|
||||||
|
notOk(rect.containsPoint(new OpenSeadragon.Point(0.6, 0.5)),
|
||||||
|
'Point 0.6,0.5 should not be inside ' + rect);
|
||||||
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -587,6 +587,44 @@
|
|||||||
viewer.open(DZI_PATH);
|
viewer.open(DZI_PATH);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
asyncTest('fitBounds with almost same zoom', function() {
|
||||||
|
var openHandler = function() {
|
||||||
|
var viewport = viewer.viewport;
|
||||||
|
var rect1 = new OpenSeadragon.Rect(0, 0, 1, 1);
|
||||||
|
viewport.fitBounds(rect1, true);
|
||||||
|
Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6,
|
||||||
|
'Bounds should be ' + rect1);
|
||||||
|
|
||||||
|
// Zoom and pan
|
||||||
|
var rect2 = new OpenSeadragon.Rect(1, 1, 1 + 1e-8, 1 + 1e-8);
|
||||||
|
viewport.fitBounds(rect2);
|
||||||
|
Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6,
|
||||||
|
'Bounds should be ' + rect2);
|
||||||
|
start();
|
||||||
|
};
|
||||||
|
viewer.addOnceHandler('open', openHandler);
|
||||||
|
viewer.open(DZI_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
|
asyncTest('fitBounds with big rectangle', function() {
|
||||||
|
var openHandler = function() {
|
||||||
|
var viewport = viewer.viewport;
|
||||||
|
var rect1 = new OpenSeadragon.Rect(0, 0, 1e9, 1e9);
|
||||||
|
viewport.fitBounds(rect1, true);
|
||||||
|
Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6,
|
||||||
|
'Bounds should be ' + rect1);
|
||||||
|
|
||||||
|
// Zoom and pan
|
||||||
|
var rect2 = new OpenSeadragon.Rect(1, 1, 2e9, 2e9);
|
||||||
|
viewport.fitBounds(rect2);
|
||||||
|
Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6,
|
||||||
|
'Bounds should be ' + rect2);
|
||||||
|
start();
|
||||||
|
};
|
||||||
|
viewer.addOnceHandler('open', openHandler);
|
||||||
|
viewer.open(DZI_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
asyncTest('fitHorizontally', function(){
|
asyncTest('fitHorizontally', function(){
|
||||||
var openHandler = function(event) {
|
var openHandler = function(event) {
|
||||||
viewer.removeHandler('open', openHandler);
|
viewer.removeHandler('open', openHandler);
|
||||||
|
Loading…
Reference in New Issue
Block a user