From 10dd723637124a441c5c61e11420010321aea4f3 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Tue, 1 Nov 2016 21:43:00 +0100 Subject: [PATCH 1/4] Remove reliance on getTileAtPoint for wrapping --- src/tiledimage.js | 61 +++++++++++++++++++++++++++++++++++------------ src/tilesource.js | 13 +++++----- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index e8092083..7441c5fb 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -996,6 +996,47 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } else { this._setFullyLoaded(true); } + }, + + // 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 +1080,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()); diff --git a/src/tilesource.js b/src/tilesource.js index fa52f61e..19ea1d63 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] called with invalid point " + point.toString()); + 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); }, From 6370a6bafaedfe1548c79254c9ddb77829e32209 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 2 Nov 2016 22:35:23 +0100 Subject: [PATCH 2/4] Fix tests --- src/tilesource.js | 2 +- test/coverage.html | 1 + test/modules/basic.js | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tilesource.js b/src/tilesource.js index 19ea1d63..74d7c315 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -347,7 +347,7 @@ $.TileSource.prototype = { 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] called with invalid point " + point.toString()); + $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point."); var widthScaled = this.dimensions.x * this.getLevelScale(level); var pixelX = point.x * widthScaled; 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(); From b162f197ea396be72e3cfe5f237687524f6bba6a Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 5 Nov 2016 17:34:36 +0100 Subject: [PATCH 3/4] Add TileSource.getTileAtPoint test --- test/modules/multi-image.js | 2 +- test/modules/tilesource.js | 59 ++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 11 deletions(-) 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/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)); + } + }); + }()); From 4a1ae356315b0ae2ad47572c587d04ea8dcef8c7 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 6 Nov 2016 15:31:36 +0100 Subject: [PATCH 4/4] Add TiledImage._getCornerTiles unit tests --- test/modules/tiledimage.js | 155 +++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) 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) + ) + }); + })();