diff --git a/src/tiledimage.js b/src/tiledimage.js
index e8092083..4291bf85 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -146,6 +146,7 @@ $.TiledImage = function( options ) {
_midDraw: false, // Is the tiledImage currently updating the viewport?
_needsDraw: true, // Does the tiledImage need to update the viewport again?
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
+ _tilesLoading: 0, // The number of pending tile requests.
//configurable settings
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime,
@@ -898,6 +899,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
// private
_updateViewport: function() {
this._needsDraw = false;
+ this._tilesLoading = 0;
// Reset tile's internal drawn state
while (this.lastDrawn.length > 0) {
@@ -994,8 +996,49 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._needsDraw = true;
this._setFullyLoaded(false);
} else {
- this._setFullyLoaded(true);
+ this._setFullyLoaded(this._tilesLoading === 0);
}
+ },
+
+ // private
+ _getCornerTiles: function(level, topLeftBound, bottomRightBound) {
+ var leftX;
+ var rightX;
+ if (this.wrapHorizontal) {
+ leftX = $.positiveModulo(topLeftBound.x, 1);
+ rightX = $.positiveModulo(bottomRightBound.x, 1);
+ } else {
+ leftX = Math.max(0, topLeftBound.x);
+ rightX = Math.min(1, bottomRightBound.x);
+ }
+ var topY;
+ var bottomY;
+ var aspectRatio = 1 / this.source.aspectRatio;
+ if (this.wrapVertical) {
+ topY = $.positiveModulo(topLeftBound.y, aspectRatio);
+ bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);
+ } else {
+ topY = Math.max(0, topLeftBound.y);
+ bottomY = Math.min(aspectRatio, bottomRightBound.y);
+ }
+
+ var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));
+ var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));
+ var numTiles = this.source.getNumTiles(level);
+
+ if (this.wrapHorizontal) {
+ topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);
+ bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);
+ }
+ if (this.wrapVertical) {
+ topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);
+ bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);
+ }
+
+ return {
+ topLeft: topLeftTile,
+ bottomRight: bottomRightTile,
+ };
}
});
@@ -1039,23 +1082,13 @@ function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
});
}
- //OK, a new drawing so do your calculations
- var topLeftTile = tiledImage.source.getTileAtPoint(level, topLeftBound);
- var bottomRightTile = tiledImage.source.getTileAtPoint(level, bottomRightBound);
- var numberOfTiles = tiledImage.source.getNumTiles(level);
-
resetCoverage(tiledImage.coverage, level);
- if (!tiledImage.wrapHorizontal) {
- // Adjust for floating point error
- topLeftTile.x = Math.max(topLeftTile.x, 0);
- bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1);
- }
- if (!tiledImage.wrapVertical) {
- // Adjust for floating point error
- topLeftTile.y = Math.max(topLeftTile.y, 0);
- bottomRightTile.y = Math.min(bottomRightTile.y, numberOfTiles.y - 1);
- }
+ //OK, a new drawing so do your calculations
+ var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);
+ var topLeftTile = cornerTiles.topLeft;
+ var bottomRightTile = cornerTiles.bottomRight;
+ var numberOfTiles = tiledImage.source.getNumTiles(level);
var viewportCenter = tiledImage.viewport.pixelFromPoint(
tiledImage.viewport.getCenter());
@@ -1178,7 +1211,7 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
}
} else if ( tile.loading ) {
// the tile is already in the download queue
- // thanks josh1093 for finally translating this typo
+ tiledImage._tilesLoading++;
} else {
best = compareTiles( best, tile );
}
diff --git a/src/tilesource.js b/src/tilesource.js
index fa52f61e..74d7c315 100644
--- a/src/tilesource.js
+++ b/src/tilesource.js
@@ -345,18 +345,17 @@ $.TileSource.prototype = {
* @param {OpenSeadragon.Point} point
*/
getTileAtPoint: function(level, point) {
+ var validPoint = point.x >= 0 && point.x <= 1 &&
+ point.y >= 0 && point.y <= 1 / this.aspectRatio;
+ $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
+
var widthScaled = this.dimensions.x * this.getLevelScale(level);
- var pixelX = $.positiveModulo(point.x, 1) * widthScaled;
- var pixelY = $.positiveModulo(point.y, 1 / this.aspectRatio) * widthScaled;
+ var pixelX = point.x * widthScaled;
+ var pixelY = point.y * widthScaled;
var x = Math.floor(pixelX / this.getTileWidth());
var y = Math.floor(pixelY / this.getTileHeight());
- // Fix for wrapping
- var numTiles = this.getNumTiles(level);
- x += numTiles.x * Math.floor(point.x);
- y += numTiles.y * Math.floor(point.y * this.aspectRatio);
-
return new $.Point(x, y);
},
diff --git a/test/coverage.html b/test/coverage.html
index b04d5fda..65711abc 100644
--- a/test/coverage.html
+++ b/test/coverage.html
@@ -32,6 +32,7 @@
+
diff --git a/test/modules/basic.js b/test/modules/basic.js
index c6164560..4d787ab4 100644
--- a/test/modules/basic.js
+++ b/test/modules/basic.js
@@ -331,7 +331,7 @@
height: 155
} ]
} );
- viewer.addHandler('tile-drawn', function() {
+ viewer.addOnceHandler('tile-drawn', function() {
ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should be tainted.");
start();
@@ -355,7 +355,7 @@
height: 155
} ]
} );
- viewer.addHandler('tile-drawn', function() {
+ viewer.addOnceHandler('tile-drawn', function() {
ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should not be tainted.");
start();
@@ -385,7 +385,7 @@
},
crossOriginPolicy : false
} );
- viewer.addHandler('tile-drawn', function() {
+ viewer.addOnceHandler('tile-drawn', function() {
ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should be tainted.");
start();
@@ -414,7 +414,7 @@
crossOriginPolicy : "Anonymous"
}
} );
- viewer.addHandler('tile-drawn', function() {
+ viewer.addOnceHandler('tile-drawn', function() {
ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should not be tainted.");
start();
diff --git a/test/modules/multi-image.js b/test/modules/multi-image.js
index 4cbd5211..1dc9cbca 100644
--- a/test/modules/multi-image.js
+++ b/test/modules/multi-image.js
@@ -217,7 +217,7 @@
var firstImage = viewer.world.getItemAt(0);
firstImage.addHandler('fully-loaded-change', function() {
var imageData = viewer.drawer.context.getImageData(0, 0,
- 500 * OpenSeadragon.pixelDensityRatio, 500 * density);
+ 500 * density, 500 * density);
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250 * density, 250 * density);
diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js
index 20171432..1b05b557 100644
--- a/test/modules/tiledimage.js
+++ b/test/modules/tiledimage.js
@@ -515,4 +515,159 @@
}]);
});
+ // PhantomJS is missing Function.prototype.bind
+ function bind(func, _this) {
+ return function() {
+ return func.apply(_this, arguments);
+ };
+ }
+
+ test('_getCornerTiles without wrapping', function() {
+ var tiledImageMock = {
+ wrapHorizontal: false,
+ wrapVertical: false,
+ source: new OpenSeadragon.TileSource({
+ width: 1500,
+ height: 1000,
+ tileWidth: 200,
+ tileHeight: 150,
+ tileOverlap: 1,
+ }),
+ };
+ var _getCornerTiles = bind(
+ OpenSeadragon.TiledImage.prototype._getCornerTiles,
+ tiledImageMock);
+
+ function assertCornerTiles(topLeftBound, bottomRightBound,
+ expectedTopLeft, expectedBottomRight) {
+ var cornerTiles = _getCornerTiles(11, topLeftBound, bottomRightBound);
+ ok(cornerTiles.topLeft.equals(expectedTopLeft),
+ 'Top left tile should be ' + expectedTopLeft.toString() +
+ ' found ' + cornerTiles.topLeft.toString());
+ ok(cornerTiles.bottomRight.equals(expectedBottomRight),
+ 'Bottom right tile should be ' + expectedBottomRight.toString() +
+ ' found ' + cornerTiles.bottomRight.toString());
+ }
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(1, 10 / 15),
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(7, 6)
+ )
+
+ // Floating point errors should be handled
+ assertCornerTiles(
+ new OpenSeadragon.Point(-1e-14, -1e-14),
+ new OpenSeadragon.Point(1 + 1e-14, 10 / 15 + 1e-14),
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(7, 6)
+ )
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(0.3, 0.5),
+ new OpenSeadragon.Point(0.5, 0.6),
+ new OpenSeadragon.Point(2, 5),
+ new OpenSeadragon.Point(3, 6)
+ )
+ });
+
+ test('_getCornerTiles with horizontal wrapping', function() {
+ var tiledImageMock = {
+ wrapHorizontal: true,
+ wrapVertical: false,
+ source: new OpenSeadragon.TileSource({
+ width: 1500,
+ height: 1000,
+ tileWidth: 200,
+ tileHeight: 150,
+ tileOverlap: 1,
+ }),
+ };
+ var _getCornerTiles = bind(
+ OpenSeadragon.TiledImage.prototype._getCornerTiles,
+ tiledImageMock);
+
+ function assertCornerTiles(topLeftBound, bottomRightBound,
+ expectedTopLeft, expectedBottomRight) {
+ var cornerTiles = _getCornerTiles(11, topLeftBound, bottomRightBound);
+ ok(cornerTiles.topLeft.equals(expectedTopLeft),
+ 'Top left tile should be ' + expectedTopLeft.toString() +
+ ' found ' + cornerTiles.topLeft.toString());
+ ok(cornerTiles.bottomRight.equals(expectedBottomRight),
+ 'Bottom right tile should be ' + expectedBottomRight.toString() +
+ ' found ' + cornerTiles.bottomRight.toString());
+ }
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(1, 10 / 15),
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(8, 6)
+ )
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(-1, 0),
+ new OpenSeadragon.Point(0.5, 10 / 15 + 1e-14),
+ new OpenSeadragon.Point(-8, 0),
+ new OpenSeadragon.Point(3, 6)
+ )
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(1.3, 0.5),
+ new OpenSeadragon.Point(1.5, 0.6),
+ new OpenSeadragon.Point(10, 5),
+ new OpenSeadragon.Point(11, 6)
+ )
+ });
+
+ test('_getCornerTiles with vertical wrapping', function() {
+ var tiledImageMock = {
+ wrapHorizontal: false,
+ wrapVertical: true,
+ source: new OpenSeadragon.TileSource({
+ width: 1500,
+ height: 1000,
+ tileWidth: 200,
+ tileHeight: 150,
+ tileOverlap: 1,
+ }),
+ };
+ var _getCornerTiles = bind(
+ OpenSeadragon.TiledImage.prototype._getCornerTiles,
+ tiledImageMock);
+
+ function assertCornerTiles(topLeftBound, bottomRightBound,
+ expectedTopLeft, expectedBottomRight) {
+ var cornerTiles = _getCornerTiles(11, topLeftBound, bottomRightBound);
+ ok(cornerTiles.topLeft.equals(expectedTopLeft),
+ 'Top left tile should be ' + expectedTopLeft.toString() +
+ ' found ' + cornerTiles.topLeft.toString());
+ ok(cornerTiles.bottomRight.equals(expectedBottomRight),
+ 'Bottom right tile should be ' + expectedBottomRight.toString() +
+ ' found ' + cornerTiles.bottomRight.toString());
+ }
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(1, 10 / 15),
+ new OpenSeadragon.Point(0, 0),
+ new OpenSeadragon.Point(7, 7)
+ )
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(0, -10 / 15 / 2),
+ new OpenSeadragon.Point(0.5, 0.5),
+ new OpenSeadragon.Point(0, -4),
+ new OpenSeadragon.Point(3, 5)
+ )
+
+ assertCornerTiles(
+ new OpenSeadragon.Point(0, 10 / 15 + 0.1),
+ new OpenSeadragon.Point(0.3, 10 / 15 + 0.3),
+ new OpenSeadragon.Point(0, 7),
+ new OpenSeadragon.Point(2, 9)
+ )
+ });
+
})();
diff --git a/test/modules/tilesource.js b/test/modules/tilesource.js
index 59d8b77f..71fb053d 100644
--- a/test/modules/tilesource.js
+++ b/test/modules/tilesource.js
@@ -1,32 +1,32 @@
/* global module, ok, equal, start, test, testLog, Util */
(function() {
-
+
module('TileSource', {
setup: function() {
testLog.reset();
}
});
-
-
+
+
test("should set sane tile size defaults", function() {
var source = new OpenSeadragon.TileSource();
-
+
equal(source.getTileWidth(), 0, "getTileWidth() should return 0 if not provided a size");
equal(source.getTileHeight(), 0, "getTileHeight() should return 0 if not provided a size");
});
-
+
test("providing tileSize", function(){
var tileSize = 256,
source = new OpenSeadragon.TileSource({
tileSize: tileSize
});
-
+
equal(source.tileSize, undefined, "tileSize should not be set on the tileSource");
equal(source.getTileWidth(), tileSize, "getTileWidth() should equal tileSize");
equal(source.getTileHeight(), tileSize, "getTileHeight() should equal tileSize");
});
-
-
+
+
test("providing tileWidth and tileHeight", function(){
var tileWidth = 256,
tileHeight = 512,
@@ -34,7 +34,7 @@
tileWidth: tileWidth,
tileHeight: tileHeight
});
-
+
equal(source._tileWidth, tileWidth, "tileWidth option should set _tileWidth");
equal(source._tileHeight, tileHeight, "tileHeight option should set _tileHeight");
equal(source.tileWidth, undefined, "tileWidth should be renamed _tileWidth");
@@ -42,10 +42,49 @@
equal(source.getTileWidth(), tileWidth, "getTileWidth() should equal tileWidth");
equal(source.getTileHeight(), tileHeight, "getTileHeight() should equal tileHeight");
});
-
+
test('getTileSize() deprecation', function() {
var source = new OpenSeadragon.TileSource();
Util.testDeprecation(source, 'getTileSize');
});
+ test('getTileAtPoint', function() {
+ var tileSource = new OpenSeadragon.TileSource({
+ width: 1500,
+ height: 1000,
+ tileWidth: 200,
+ tileHeight: 150,
+ tileOverlap: 1,
+ });
+
+ equal(tileSource.maxLevel, 11, "The max level should be 11.");
+
+ function assertTileAtPoint(level, position, expected) {
+ var actual = tileSource.getTileAtPoint(level, position);
+ ok(actual.equals(expected), "The tile at level " + level +
+ ", position " + position.toString() +
+ " should be tile " + expected.toString() +
+ " got " + actual.toString());
+ }
+
+ assertTileAtPoint(11, new OpenSeadragon.Point(0, 0), new OpenSeadragon.Point(0, 0));
+ assertTileAtPoint(11, new OpenSeadragon.Point(0.5, 0.5), new OpenSeadragon.Point(3, 5));
+ assertTileAtPoint(11, new OpenSeadragon.Point(1, 10 / 15), new OpenSeadragon.Point(7, 6));
+
+ assertTileAtPoint(10, new OpenSeadragon.Point(0, 0), new OpenSeadragon.Point(0, 0));
+ assertTileAtPoint(10, new OpenSeadragon.Point(0.5, 0.5), new OpenSeadragon.Point(1, 2));
+ assertTileAtPoint(10, new OpenSeadragon.Point(1, 10 / 15), new OpenSeadragon.Point(3, 3));
+
+ assertTileAtPoint(9, new OpenSeadragon.Point(0, 0), new OpenSeadragon.Point(0, 0));
+ assertTileAtPoint(9, new OpenSeadragon.Point(0.5, 0.5), new OpenSeadragon.Point(0, 1));
+ assertTileAtPoint(9, new OpenSeadragon.Point(1, 10 / 15), new OpenSeadragon.Point(1, 1));
+
+ // For all other levels, there is only one tile.
+ for (var level = 8; level >= 0; level--) {
+ assertTileAtPoint(level, new OpenSeadragon.Point(0, 0), new OpenSeadragon.Point(0, 0));
+ assertTileAtPoint(level, new OpenSeadragon.Point(0.5, 0.5), new OpenSeadragon.Point(0, 0));
+ assertTileAtPoint(level, new OpenSeadragon.Point(1, 10 / 15), new OpenSeadragon.Point(0, 0));
+ }
+ });
+
}());