From c0417463663272a30504c17370251a77481492b9 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 1 Nov 2015 10:23:02 -0500 Subject: [PATCH 01/15] Add ImageTileSource. Fix #467 --- Gruntfile.js | 1 + src/imagetilesource.js | 174 ++++++++++++++++++++++++++++++++++++++++ src/viewer.js | 18 ++++- test/coverage.html | 1 + test/modules/formats.js | 8 ++ 5 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 src/imagetilesource.js diff --git a/Gruntfile.js b/Gruntfile.js index 06511e31..8c7ff60a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -39,6 +39,7 @@ module.exports = function(grunt) { "src/osmtilesource.js", "src/tmstilesource.js", "src/legacytilesource.js", + "src/imagetilesource.js", "src/tilesourcecollection.js", "src/button.js", "src/buttongroup.js", diff --git a/src/imagetilesource.js b/src/imagetilesource.js new file mode 100644 index 00000000..a7f025b3 --- /dev/null +++ b/src/imagetilesource.js @@ -0,0 +1,174 @@ +/* + * OpenSeadragon - ImageTileSource + * + * Copyright (C) 2009 CodePlex Foundation + * Copyright (C) 2010-2013 OpenSeadragon contributors + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of CodePlex Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function ($) { + + /** + * @class ImageTileSource + * @classdesc The ImageTileSource allows simple image to be loaded + * into an OpenSeadragon Viewer. + * + * @memberof OpenSeadragon + * @extends OpenSeadragon.TileSource + * @param {Object} options Options object. + * @property {String} options.url URL of the image + */ + $.ImageTileSource = function (options) { + + $.TileSource.apply(this, [options]); + + }; + + $.extend($.ImageTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ImageTileSource.prototype */{ + /** + * Determine if the data and/or url imply the image service is supported by + * this tile source. + * @function + * @param {Object|Array} data + * @param {String} optional - url + */ + supports: function (data, url) { + return data.type && data.type === "image"; + }, + /** + * + * @function + * @param {Image} image - the actual image + * @param {String} dataUrl - the url the image was retreived from, if any. + * @return {Object} options - A dictionary of keyword arguments sufficient + * to configure this tile sources constructor. + */ + configure: function (image, dataUrl) { + return image; + }, + /** + * Responsible for retrieving, and caching the + * image metadata pertinent to this TileSources implementation. + * @function + * @param {String} url + * @throws {Error} + */ + getImageInfo: function (url) { + var image = new Image(); + var _this = this; + + image.addEventListener('load', function () { + _this.width = image.naturalWidth; + _this.height = image.naturalHeight; + _this.aspectRatio = _this.width / _this.height; + _this.dimensions = new $.Point(_this.width, _this.height); + _this._tileWidth = _this.width; + _this._tileHeight = _this.height; + _this.tileOverlap = 0; + _this.minLevel = 0; + _this.maxLevel = 0; + + _this.ready = true; + /** + * Raised when a TileSource is opened and initialized. + * + * @event ready + * @memberof OpenSeadragon.TileSource + * @type {object} + * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. + * @property {Object} tileSource + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + _this.raiseEvent('ready', {tileSource: _this}); + }); + + image.addEventListener('error', function () { + /*** + * Raised when an error occurs loading a TileSource. + * + * @event open-failed + * @memberof OpenSeadragon.TileSource + * @type {object} + * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. + * @property {String} message + * @property {String} source + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + _this.raiseEvent('open-failed', { + message: "Error loading image at " + url, + source: url + }); + }); + + image.src = url; + }, + /** + * @function + * @param {Number} level + */ + getLevelScale: function (level) { + return level === 0 ? 1 : NaN; + }, + /** + * @function + * @param {Number} level + */ + getNumTiles: function (level) { + var scale = this.getLevelScale(level); + if (scale) { + return new $.Point(1, 1); + } else { + return new $.Point(0, 0); + } + }, + /** + * @function + * @param {Number} level + * @param {OpenSeadragon.Point} point + */ + getTileAtPoint: function (level, point) { + return new $.Point(0, 0); + }, + /** + * This method is not implemented by this class other than to throw an Error + * announcing you have to implement it. Because of the variety of tile + * server technologies, and various specifications for building image + * pyramids, this method is here to allow easy integration. + * @function + * @param {Number} level + * @param {Number} x + * @param {Number} y + * @throws {Error} + */ + getTileUrl: function (level, x, y) { + return this.url; + } + }); + +}(OpenSeadragon)); diff --git a/src/viewer.js b/src/viewer.js index d60b5320..c763cf68 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2075,14 +2075,26 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, return; } var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] ); - var readySource = new $TileSource( options ); - successCallback( readySource ); + var readySource = new $TileSource(options); + if (readySource.ready) { + successCallback(readySource); + } else { + readySource.addHandler('ready', function () { + successCallback(readySource); + }); + readySource.addHandler('open-failed', function (event) { + failCallback({ + message: event.message, + source: tileSource + }); + }); + } } } else { //can assume it's already a tile source implementation successCallback( tileSource ); } - }, 1 ); + }); } function getOverlayObject( viewer, overlay ) { diff --git a/test/coverage.html b/test/coverage.html index 65cc2818..dc857202 100644 --- a/test/coverage.html +++ b/test/coverage.html @@ -32,6 +32,7 @@ + diff --git a/test/modules/formats.js b/test/modules/formats.js index 2cef3576..5166f796 100644 --- a/test/modules/formats.js +++ b/test/modules/formats.js @@ -142,4 +142,12 @@ '}'); }); + // ---------- + asyncTest('ImageTileSource', function () { + testOpen({ + type: "image", + url: "/test/data/A.png" + }); + }); + })(); From 9a4543cd73402ab8e9536f8c012878bb4cb54725 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 1 Nov 2015 13:04:50 -0500 Subject: [PATCH 02/15] Add netbeans configs. --- .gitignore | 4 +++- nbproject/project.properties | 18 ++++++++++++++++++ nbproject/project.xml | 9 +++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 nbproject/project.properties create mode 100644 nbproject/project.xml diff --git a/.gitignore b/.gitignore index aaaabf8c..282b4dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ build/ sftp-config.json coverage/ temp/ -.idea \ No newline at end of file +.idea +/nbproject/private/ +.directory diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 00000000..422675b4 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,18 @@ +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=4 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=4 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80 +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none +auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project +auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.indent-shift-width=4 +auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.indent-shift-width=2 +auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.spaces-per-tab=2 +auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=src +browser.autorefresh.Chromium.INTEGRATED=true +browser.highlightselection.Chromium.INTEGRATED=true +file.reference.openseadragon-openseadragon=. +files.encoding=UTF-8 +site.root.folder=${file.reference.openseadragon-openseadragon} +start.file=test/demo/basic.html +web.context.root=/ diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 00000000..53613f5c --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,9 @@ + + + org.netbeans.modules.web.clientproject + + + openseadragon + + + From 3316a55b97059caeface32da35f265753387db27 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 1 Nov 2015 13:25:25 -0500 Subject: [PATCH 03/15] Add CORS support to ImageTileSource --- src/imagetilesource.js | 14 +++++++++++--- src/viewer.js | 6 +++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index a7f025b3..e398296b 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -46,6 +46,7 @@ */ $.ImageTileSource = function (options) { + this.options = options; $.TileSource.apply(this, [options]); }; @@ -64,13 +65,13 @@ /** * * @function - * @param {Image} image - the actual image + * @param {Object} options - the options * @param {String} dataUrl - the url the image was retreived from, if any. * @return {Object} options - A dictionary of keyword arguments sufficient * to configure this tile sources constructor. */ - configure: function (image, dataUrl) { - return image; + configure: function (options, dataUrl) { + return options; }, /** * Responsible for retrieving, and caching the @@ -83,6 +84,13 @@ var image = new Image(); var _this = this; + if (this.options.crossOriginPolicy) { + image.crossOriginPolicy = this.options.crossOriginPolicy; + } + if (this.options.ajaxWithCredentials) { + image.useCredentials = this.options.ajaxWithCredentials; + } + image.addEventListener('load', function () { _this.width = image.naturalWidth; _this.height = image.naturalHeight; diff --git a/src/viewer.js b/src/viewer.js index c763cf68..f59f9d1f 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2045,6 +2045,7 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, //If its still a string it means it must be a url at this point tileSource = new $.TileSource({ url: tileSource, + crossOriginPolicy: viewer.crossOriginPolicy, ajaxWithCredentials: viewer.ajaxWithCredentials, success: function( event ) { successCallback( event.tileSource ); @@ -2054,7 +2055,10 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, failCallback( event ); } ); - } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ) { + } else if ($.isPlainObject(tileSource) || tileSource.nodeType) { + if (!tileSource.crossOriginPolicy && viewer.crossOriginPolicy) { + tileSource.crossOriginPolicy = viewer.crossOriginPolicy; + } if (tileSource.ajaxWithCredentials === undefined) { tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials; } From 8e06eb56cfbc05a9bc166c341ed115f30a0fe5ff Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sun, 1 Nov 2015 15:55:39 -0500 Subject: [PATCH 04/15] Add pyramid building to the ImageTileSource. --- src/imagetilesource.js | 100 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index e398296b..4fcb0e6a 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -43,6 +43,14 @@ * @extends OpenSeadragon.TileSource * @param {Object} options Options object. * @property {String} options.url URL of the image + * @property {Boolean} options.buildPyramid If set to true, a pyramid will + * be built internally to provide a better downsampling. + * @property {String|Boolean} options.crossOriginPolicy Valid values are + * 'Anonymous', 'use-credentials', and false. If false, image requests will + * not use CORS preventing internal pyramid building for images from other + * domains. + * @property {String|Boolean} options.ajaxWithCredentials Whether to set the + * withCredentials XHR flag for AJAX requests (when loading tile sources) */ $.ImageTileSource = function (options) { @@ -85,7 +93,7 @@ var _this = this; if (this.options.crossOriginPolicy) { - image.crossOriginPolicy = this.options.crossOriginPolicy; + image.crossOrigin = this.options.crossOriginPolicy; } if (this.options.ajaxWithCredentials) { image.useCredentials = this.options.ajaxWithCredentials; @@ -100,7 +108,12 @@ _this._tileHeight = _this.height; _this.tileOverlap = 0; _this.minLevel = 0; - _this.maxLevel = 0; + + var pyramidMinWidth = _this.buildPyramid ? 1 : _this.width; + var pyramidMinHeight = _this.buildPyramid ? 1 : _this.height; + + _this.levels = buildLevels(image, pyramidMinWidth, pyramidMinHeight); + _this.maxLevel = _this.levels.length - 1; _this.ready = true; /** @@ -141,7 +154,13 @@ * @param {Number} level */ getLevelScale: function (level) { - return level === 0 ? 1 : NaN; + var levelScale = NaN; + if (level >= this.minLevel && level <= this.maxLevel) { + levelScale = + this.levels[level].width / + this.levels[this.maxLevel].width; + } + return levelScale; }, /** * @function @@ -175,8 +194,81 @@ * @throws {Error} */ getTileUrl: function (level, x, y) { - return this.url; + var url = null; + if (level >= this.minLevel && level <= this.maxLevel) { + url = this.levels[level].url; + } + return url; } }); + function buildLevels(image, minWidth, minHeight) { + var levels = [{ + url: image.src, + width: image.naturalWidth, + height: image.naturalHeight + }]; + + var currentWidth = Math.floor(image.naturalWidth / 2); + var currentHeight = Math.floor(image.naturalHeight / 2); + + if (currentWidth < minWidth || currentHeight < minHeight) { + return levels; + } + + var bigCanvas = document.createElement("canvas"); + var bigContext = bigCanvas.getContext("2d"); + + bigCanvas.width = currentWidth; + bigCanvas.height = currentHeight; + bigContext.drawImage(image, 0, 0, currentWidth, currentHeight); + + if (isCanvasTainted(bigContext)) { + // If the canvas is tainted, we can't compute the pyramid. + return levels; + } + levels.splice(0, 0, { + url: bigCanvas.toDataURL(), + width: currentWidth, + height: currentHeight + }); + + var smallCanvas = document.createElement("canvas"); + var smallContext = smallCanvas.getContext("2d"); + while (currentWidth >= minWidth * 2 && currentHeight >= minHeight * 2) { + currentWidth = Math.floor(currentWidth / 2); + currentHeight = Math.floor(currentHeight / 2); + smallCanvas.width = currentWidth; + smallCanvas.height = currentHeight; + smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight); + + levels.splice(0, 0, { + url: smallCanvas.toDataURL(), + width: currentWidth, + height: currentHeight + }); + + var tempCanvas = bigCanvas; + bigCanvas = smallCanvas; + smallCanvas = tempCanvas; + + var tempContext = bigContext; + bigContext = smallContext; + smallContext = tempContext; + } + return levels; + } + + function isCanvasTainted(context) { + var isTainted = false; + try { + // We test if the canvas is tainted by retrieving data from it. + // An exception will be raised if the canvas is tainted. + var data = context.getImageData(0, 0, 1, 1); + } catch (e) { + isTainted = true; + } + return isTainted; + } + }(OpenSeadragon)); From 14a83e1154749e33e1ee46f3bbb0dbc15a70a6cd Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 2 Nov 2015 18:35:11 -0500 Subject: [PATCH 05/15] Add IE8 support in ImageTileSource. --- src/imagetilesource.js | 144 ++++++++++++++++++++--------------------- src/openseadragon.js | 17 +++++ src/viewer.js | 4 ++ 3 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index 4fcb0e6a..72ab4917 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -43,17 +43,23 @@ * @extends OpenSeadragon.TileSource * @param {Object} options Options object. * @property {String} options.url URL of the image - * @property {Boolean} options.buildPyramid If set to true, a pyramid will - * be built internally to provide a better downsampling. + * @property {Boolean} options.buildPyramid If set to true (default), a + * pyramid will be built internally to provide a better downsampling. * @property {String|Boolean} options.crossOriginPolicy Valid values are * 'Anonymous', 'use-credentials', and false. If false, image requests will * not use CORS preventing internal pyramid building for images from other * domains. * @property {String|Boolean} options.ajaxWithCredentials Whether to set the * withCredentials XHR flag for AJAX requests (when loading tile sources) + * @property {Boolean} options.useCanvas Set to false to prevent any use of + * the canvas API. */ $.ImageTileSource = function (options) { + $.extend(options, { + buildPyramid: true, + useCanvas: true + }); this.options = options; $.TileSource.apply(this, [options]); @@ -99,7 +105,7 @@ image.useCredentials = this.options.ajaxWithCredentials; } - image.addEventListener('load', function () { + $.addEvent(image, 'load', function () { _this.width = image.naturalWidth; _this.height = image.naturalHeight; _this.aspectRatio = _this.width / _this.height; @@ -112,7 +118,8 @@ var pyramidMinWidth = _this.buildPyramid ? 1 : _this.width; var pyramidMinHeight = _this.buildPyramid ? 1 : _this.height; - _this.levels = buildLevels(image, pyramidMinWidth, pyramidMinHeight); + _this.levels = _this._buildLevels( + image, pyramidMinWidth, pyramidMinHeight); _this.maxLevel = _this.levels.length - 1; _this.ready = true; @@ -122,21 +129,23 @@ * @event ready * @memberof OpenSeadragon.TileSource * @type {object} - * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. + * @property {OpenSeadragon.TileSource} eventSource - A reference + * to the TileSource which raised the event. * @property {Object} tileSource * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent('ready', {tileSource: _this}); }); - image.addEventListener('error', function () { + $.addEvent(image, 'error', function () { /*** * Raised when an error occurs loading a TileSource. * * @event open-failed * @memberof OpenSeadragon.TileSource * @type {object} - * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. + * @property {OpenSeadragon.TileSource} eventSource - A reference + * to the TileSource which raised the event. * @property {String} message * @property {String} source * @property {?Object} userData - Arbitrary subscriber-defined object. @@ -183,15 +192,11 @@ return new $.Point(0, 0); }, /** - * This method is not implemented by this class other than to throw an Error - * announcing you have to implement it. Because of the variety of tile - * server technologies, and various specifications for building image - * pyramids, this method is here to allow easy integration. + * Retrieves a tile url * @function - * @param {Number} level - * @param {Number} x - * @param {Number} y - * @throws {Error} + * @param {Number} level Level of the tile + * @param {Number} x x coordinate of the tile + * @param {Number} y y coordinate of the tile */ getTileUrl: function (level, x, y) { var url = null; @@ -199,76 +204,71 @@ url = this.levels[level].url; } return url; - } - }); + }, + /** + * @private Build the differents levels of the pyramid if possible + * (canvas API enabled and no canvas tainting issue) + */ + _buildLevels: function (image, minWidth, minHeight) { + var levels = [{ + url: image.src, + width: image.naturalWidth, + height: image.naturalHeight + }]; - function buildLevels(image, minWidth, minHeight) { - var levels = [{ - url: image.src, - width: image.naturalWidth, - height: image.naturalHeight - }]; + if (!$.supportsCanvas || !this.useCanvas) { + return levels; + } - var currentWidth = Math.floor(image.naturalWidth / 2); - var currentHeight = Math.floor(image.naturalHeight / 2); + var currentWidth = Math.floor(image.naturalWidth / 2); + var currentHeight = Math.floor(image.naturalHeight / 2); - if (currentWidth < minWidth || currentHeight < minHeight) { - return levels; - } + if (currentWidth < minWidth || currentHeight < minHeight) { + return levels; + } - var bigCanvas = document.createElement("canvas"); - var bigContext = bigCanvas.getContext("2d"); + var bigCanvas = document.createElement("canvas"); + var bigContext = bigCanvas.getContext("2d"); - bigCanvas.width = currentWidth; - bigCanvas.height = currentHeight; - bigContext.drawImage(image, 0, 0, currentWidth, currentHeight); - - if (isCanvasTainted(bigContext)) { - // If the canvas is tainted, we can't compute the pyramid. - return levels; - } - levels.splice(0, 0, { - url: bigCanvas.toDataURL(), - width: currentWidth, - height: currentHeight - }); - - var smallCanvas = document.createElement("canvas"); - var smallContext = smallCanvas.getContext("2d"); - while (currentWidth >= minWidth * 2 && currentHeight >= minHeight * 2) { - currentWidth = Math.floor(currentWidth / 2); - currentHeight = Math.floor(currentHeight / 2); - smallCanvas.width = currentWidth; - smallCanvas.height = currentHeight; - smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight); + bigCanvas.width = currentWidth; + bigCanvas.height = currentHeight; + bigContext.drawImage(image, 0, 0, currentWidth, currentHeight); + if ($.isCanvasTainted(bigContext.canvas)) { + // If the canvas is tainted, we can't compute the pyramid. + return levels; + } levels.splice(0, 0, { - url: smallCanvas.toDataURL(), + url: bigCanvas.toDataURL(), width: currentWidth, height: currentHeight }); - var tempCanvas = bigCanvas; - bigCanvas = smallCanvas; - smallCanvas = tempCanvas; + var smallCanvas = document.createElement("canvas"); + var smallContext = smallCanvas.getContext("2d"); + while (currentWidth >= minWidth * 2 && currentHeight >= minHeight * 2) { + currentWidth = Math.floor(currentWidth / 2); + currentHeight = Math.floor(currentHeight / 2); + smallCanvas.width = currentWidth; + smallCanvas.height = currentHeight; + smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight); - var tempContext = bigContext; - bigContext = smallContext; - smallContext = tempContext; - } - return levels; - } + levels.splice(0, 0, { + url: smallCanvas.toDataURL(), + width: currentWidth, + height: currentHeight + }); - function isCanvasTainted(context) { - var isTainted = false; - try { - // We test if the canvas is tainted by retrieving data from it. - // An exception will be raised if the canvas is tainted. - var data = context.getImageData(0, 0, 1, 1); - } catch (e) { - isTainted = true; + var tempCanvas = bigCanvas; + bigCanvas = smallCanvas; + smallCanvas = tempCanvas; + + var tempContext = bigContext; + bigContext = smallContext; + smallContext = tempContext; + } + return levels; } - return isTainted; - } + }); }(OpenSeadragon)); diff --git a/src/openseadragon.js b/src/openseadragon.js index 6b36327b..78eaf27c 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -839,6 +839,23 @@ if (typeof define === 'function' && define.amd) { canvasElement.getContext( '2d' ) ); }()); + /** + * Test whether the submitted canvas is tainted or not. + * @argument {Canvas} canvas The canvas to test. + * @returns {Boolean} True if the canvas is tainted. + */ + $.isCanvasTainted = function(canvas) { + var isTainted = false; + try { + // We test if the canvas is tainted by retrieving data from it. + // An exception will be raised if the canvas is tainted. + var data = canvas.getContext('2d').getImageData(0, 0, 1, 1); + } catch (e) { + isTainted = true; + } + return isTainted; + }; + /** * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density. Defaults to 1 if canvas isn't supported by the browser. * @member {Number} pixelDensityRatio diff --git a/src/viewer.js b/src/viewer.js index f59f9d1f..b0e9381f 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2047,6 +2047,7 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, url: tileSource, crossOriginPolicy: viewer.crossOriginPolicy, ajaxWithCredentials: viewer.ajaxWithCredentials, + useCanvas: viewer.useCanvas, success: function( event ) { successCallback( event.tileSource ); } @@ -2062,6 +2063,9 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, if (tileSource.ajaxWithCredentials === undefined) { tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials; } + if (tileSource.useCanvas === undefined) { + tileSource.useCanvas = viewer.useCanvas; + } if ( $.isFunction( tileSource.getTileUrl ) ) { //Custom tile source From 37ca9235f8ec1fef4117e26bc25dcf0134177654 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 2 Nov 2015 19:42:14 -0500 Subject: [PATCH 06/15] Avoid using data urls in ImageTileSource. --- src/imagetilesource.js | 27 ++++++++++++++++++--------- src/tile.js | 17 +++++++++++++---- src/tiledimage.js | 34 ++++++++++++++++++++++------------ 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index 72ab4917..9f8796db 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -205,6 +205,20 @@ } return url; }, + /** + * Retrieves a tile context 2D + * @function + * @param {Number} level Level of the tile + * @param {Number} x x coordinate of the tile + * @param {Number} y y coordinate of the tile + */ + getContext2D: function (level, x, y) { + var context = null; + if (level >= this.minLevel && level <= this.maxLevel) { + context = this.levels[level].context2D; + } + return context; + }, /** * @private Build the differents levels of the pyramid if possible * (canvas API enabled and no canvas tainting issue) @@ -239,33 +253,28 @@ return levels; } levels.splice(0, 0, { - url: bigCanvas.toDataURL(), + context2D: bigContext, width: currentWidth, height: currentHeight }); - var smallCanvas = document.createElement("canvas"); - var smallContext = smallCanvas.getContext("2d"); while (currentWidth >= minWidth * 2 && currentHeight >= minHeight * 2) { currentWidth = Math.floor(currentWidth / 2); currentHeight = Math.floor(currentHeight / 2); + var smallCanvas = document.createElement("canvas"); + var smallContext = smallCanvas.getContext("2d"); smallCanvas.width = currentWidth; smallCanvas.height = currentHeight; smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight); levels.splice(0, 0, { - url: smallCanvas.toDataURL(), + context2D: smallContext, width: currentWidth, height: currentHeight }); - var tempCanvas = bigCanvas; bigCanvas = smallCanvas; - smallCanvas = tempCanvas; - - var tempContext = bigContext; bigContext = smallContext; - smallContext = tempContext; } return levels; } diff --git a/src/tile.js b/src/tile.js index ad018ba7..97d88e17 100644 --- a/src/tile.js +++ b/src/tile.js @@ -45,8 +45,10 @@ * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has * this tile failed to load? ) * @param {String} url The URL of this tile's image. + * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it + * is provided directly by the tile source. */ -$.Tile = function(level, x, y, bounds, exists, url) { +$.Tile = function(level, x, y, bounds, exists, url, context2D) { /** * The zoom level this tile belongs to. * @member {Number} level @@ -83,6 +85,12 @@ $.Tile = function(level, x, y, bounds, exists, url) { * @memberof OpenSeadragon.Tile# */ this.url = url; + /** + * The context2D of this tile if it is provided directly by the tile source. + * @member {CanvasRenderingContext2D} context2D + * @memberOf OpenSeadragon.Tile# + */ + this.context2D = context2D; /** * Is this tile loaded? * @member {Boolean} loaded @@ -247,14 +255,14 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{ size = this.size, rendered; - if (!this.cacheImageRecord) { + if (!this.context2D && !this.cacheImageRecord) { $.console.warn( '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', this.toString()); return; } - rendered = this.cacheImageRecord.getRenderedContext(); + rendered = this.context2D || this.cacheImageRecord.getRenderedContext(); if ( !this.loaded || !rendered ){ $.console.warn( @@ -273,7 +281,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{ //ie its done fading or fading is turned off, and if we are drawing //an image with an alpha channel, then the only way //to avoid seeing the tile underneath is to clear the rectangle - if( context.globalAlpha == 1 && this.url.match('.png') ){ + if (context.globalAlpha === 1 && + (this.context2D || this.url.match('.png'))) { //clearing only the inside of the rectangle occupied //by the png prevents edge flikering context.clearRect( diff --git a/src/tiledimage.js b/src/tiledimage.js index a6943de4..410edbd2 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -791,7 +791,7 @@ function updateViewport( tiledImage ) { drawTiles( tiledImage, tiledImage.lastDrawn ); // Load the new 'best' tile - if ( best ) { + if (best && !best.context2D) { loadTile( tiledImage, best, currentTime ); } @@ -936,10 +936,14 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity ); if (!tile.loaded) { - var imageRecord = tiledImage._tileCache.getImageRecord(tile.url); - if (imageRecord) { - var image = imageRecord.getImage(); - setTileLoaded(tiledImage, tile, image); + if (tile.context2D) { + setTileLoaded(tiledImage, tile); + } else { + var imageRecord = tiledImage._tileCache.getImageRecord(tile.url); + if (imageRecord) { + var image = imageRecord.getImage(); + setTileLoaded(tiledImage, tile, image); + } } } @@ -972,6 +976,7 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid bounds, exists, url, + context2D, tile; if ( !tilesMatrix[ level ] ) { @@ -987,6 +992,8 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid bounds = tileSource.getTileBounds( level, xMod, yMod ); exists = tileSource.tileExists( level, xMod, yMod ); url = tileSource.getTileUrl( level, xMod, yMod ); + context2D = tileSource.getContext2D ? + tileSource.getContext2D(level, xMod, yMod) : undefined; bounds.x += ( x - xMod ) / numTiles.x; bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y); @@ -997,7 +1004,8 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid y, bounds, exists, - url + url, + context2D ); } @@ -1076,12 +1084,14 @@ function setTileLoaded(tiledImage, tile, image, cutoff) { if (increment === 0) { tile.loading = false; tile.loaded = true; - tiledImage._tileCache.cacheTile({ - image: image, - tile: tile, - cutoff: cutoff, - tiledImage: tiledImage - }); + if (!tile.context2D) { + tiledImage._tileCache.cacheTile({ + image: image, + tile: tile, + cutoff: cutoff, + tiledImage: tiledImage + }); + } tiledImage._needsDraw = true; } } From 0c75b450750a8082501ef9f4f0cbb082f73692c9 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 2 Nov 2015 19:57:13 -0500 Subject: [PATCH 07/15] Save the context of the full image as well. --- src/imagetilesource.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index 9f8796db..7563ce40 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -234,12 +234,8 @@ return levels; } - var currentWidth = Math.floor(image.naturalWidth / 2); - var currentHeight = Math.floor(image.naturalHeight / 2); - - if (currentWidth < minWidth || currentHeight < minHeight) { - return levels; - } + var currentWidth = image.naturalWidth; + var currentHeight = image.naturalHeight; var bigCanvas = document.createElement("canvas"); var bigContext = bigCanvas.getContext("2d"); @@ -247,16 +243,12 @@ bigCanvas.width = currentWidth; bigCanvas.height = currentHeight; bigContext.drawImage(image, 0, 0, currentWidth, currentHeight); + levels[0].context2D = bigContext; if ($.isCanvasTainted(bigContext.canvas)) { // If the canvas is tainted, we can't compute the pyramid. return levels; } - levels.splice(0, 0, { - context2D: bigContext, - width: currentWidth, - height: currentHeight - }); while (currentWidth >= minWidth * 2 && currentHeight >= minHeight * 2) { currentWidth = Math.floor(currentWidth / 2); From 03ed52c5b46e9bae795d023f76f1ffeb895e090c Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 4 Nov 2015 17:49:13 -0500 Subject: [PATCH 08/15] Remove duplicate isCanvasTainted function in tests. --- test/modules/basic.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/test/modules/basic.js b/test/modules/basic.js index 7f095777..8b7a513b 100644 --- a/test/modules/basic.js +++ b/test/modules/basic.js @@ -314,23 +314,11 @@ var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); - callback(!isCanvasTainted(ctx)); + callback(!OpenSeadragon.isCanvasTainted(canvas)); }; img.src = corsImg; } - function isCanvasTainted(context) { - var isTainted = false; - try { - // We test if the canvas is tainted by retrieving data from it. - // An exception will be raised if the canvas is tainted. - var url = context.getImageData(0, 0, 1, 1); - } catch (e) { - isTainted = true; - } - return isTainted; - } - asyncTest( 'CrossOriginPolicyMissing', function () { viewer.crossOriginPolicy = false; @@ -343,7 +331,8 @@ } ] } ); viewer.addHandler('tile-drawn', function() { - ok(isCanvasTainted(viewer.drawer.context), "Canvas should be tainted."); + ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas), + "Canvas should be tainted."); start(); }); @@ -366,7 +355,8 @@ } ] } ); viewer.addHandler('tile-drawn', function() { - ok(!isCanvasTainted(viewer.drawer.context), "Canvas should not be tainted."); + ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas), + "Canvas should not be tainted."); start(); }); } From 67b0d9bd992829ee360151bdc67733093ee4d151 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Wed, 4 Nov 2015 18:47:41 -0500 Subject: [PATCH 09/15] Fix options handling and improve doc. --- src/imagetilesource.js | 55 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index 7563ce40..1f792510 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -36,31 +36,30 @@ /** * @class ImageTileSource - * @classdesc The ImageTileSource allows simple image to be loaded + * @classdesc The ImageTileSource allows a simple image to be loaded * into an OpenSeadragon Viewer. * * @memberof OpenSeadragon * @extends OpenSeadragon.TileSource * @param {Object} options Options object. - * @property {String} options.url URL of the image - * @property {Boolean} options.buildPyramid If set to true (default), a + * @param {String} options.url URL of the image + * @param {Boolean} [options.buildPyramid=true] If set to true (default), a * pyramid will be built internally to provide a better downsampling. - * @property {String|Boolean} options.crossOriginPolicy Valid values are + * @param {String|Boolean} options.crossOriginPolicy Valid values are * 'Anonymous', 'use-credentials', and false. If false, image requests will * not use CORS preventing internal pyramid building for images from other - * domains. - * @property {String|Boolean} options.ajaxWithCredentials Whether to set the - * withCredentials XHR flag for AJAX requests (when loading tile sources) - * @property {Boolean} options.useCanvas Set to false to prevent any use of - * the canvas API. + * domains. Inherited from the viewer if not set. + * @param {String|Boolean} options.ajaxWithCredentials Whether to set the + * withCredentials XHR flag for AJAX requests (when loading tile sources). + * Inherited from the viewer if not set. + * @param {Boolean} options.useCanvas Set to false to prevent any use of + * the canvas API. Inherited from the viewer if not set. */ $.ImageTileSource = function (options) { - $.extend(options, { - buildPyramid: true, - useCanvas: true - }); - this.options = options; + options = $.extend({ + buildPyramid: true + }, options); $.TileSource.apply(this, [options]); }; @@ -98,11 +97,11 @@ var image = new Image(); var _this = this; - if (this.options.crossOriginPolicy) { - image.crossOrigin = this.options.crossOriginPolicy; + if (this.crossOriginPolicy) { + image.crossOrigin = this.crossOriginPolicy; } - if (this.options.ajaxWithCredentials) { - image.useCredentials = this.options.ajaxWithCredentials; + if (this.ajaxWithCredentials) { + image.useCredentials = this.ajaxWithCredentials; } $.addEvent(image, 'load', function () { @@ -114,12 +113,7 @@ _this._tileHeight = _this.height; _this.tileOverlap = 0; _this.minLevel = 0; - - var pyramidMinWidth = _this.buildPyramid ? 1 : _this.width; - var pyramidMinHeight = _this.buildPyramid ? 1 : _this.height; - - _this.levels = _this._buildLevels( - image, pyramidMinWidth, pyramidMinHeight); + _this.levels = _this._buildLevels(image); _this.maxLevel = _this.levels.length - 1; _this.ready = true; @@ -223,14 +217,14 @@ * @private Build the differents levels of the pyramid if possible * (canvas API enabled and no canvas tainting issue) */ - _buildLevels: function (image, minWidth, minHeight) { + _buildLevels: function (image) { var levels = [{ url: image.src, width: image.naturalWidth, height: image.naturalHeight }]; - if (!$.supportsCanvas || !this.useCanvas) { + if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { return levels; } @@ -243,14 +237,19 @@ bigCanvas.width = currentWidth; bigCanvas.height = currentHeight; bigContext.drawImage(image, 0, 0, currentWidth, currentHeight); + // We cache the context of the highest level because the browser + // is a lot faster at downsampling something it already has + // downsampled before. levels[0].context2D = bigContext; - if ($.isCanvasTainted(bigContext.canvas)) { + if ($.isCanvasTainted(bigCanvas)) { // If the canvas is tainted, we can't compute the pyramid. return levels; } - while (currentWidth >= minWidth * 2 && currentHeight >= minHeight * 2) { + // We build smaller levels until either width or height becomes + // 1 pixel wide. + while (currentWidth >= 2 && currentHeight >= 2) { currentWidth = Math.floor(currentWidth / 2); currentHeight = Math.floor(currentHeight / 2); var smallCanvas = document.createElement("canvas"); From b85d0674e63ca3fba1039ae6203a4fc192fd7529 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 5 Nov 2015 22:04:21 -0500 Subject: [PATCH 10/15] Add support to open an ImageTileSource with it constructor. --- src/imagetilesource.js | 25 +++++++++++++++++-------- src/viewer.js | 33 ++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index 1f792510..b058dfc0 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -38,6 +38,13 @@ * @class ImageTileSource * @classdesc The ImageTileSource allows a simple image to be loaded * into an OpenSeadragon Viewer. + * There are 2 ways to open an ImageTileSource: + * 1. viewer.open({type: 'image', url: fooUrl}); + * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl})); + * + * With the first syntax, the crossOriginPolicy, ajaxWithCredentials and + * useCanvas options are inherited from the viewer if they are not + * specified directly in the options object. * * @memberof OpenSeadragon * @extends OpenSeadragon.TileSource @@ -45,20 +52,22 @@ * @param {String} options.url URL of the image * @param {Boolean} [options.buildPyramid=true] If set to true (default), a * pyramid will be built internally to provide a better downsampling. - * @param {String|Boolean} options.crossOriginPolicy Valid values are + * @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are * 'Anonymous', 'use-credentials', and false. If false, image requests will * not use CORS preventing internal pyramid building for images from other - * domains. Inherited from the viewer if not set. - * @param {String|Boolean} options.ajaxWithCredentials Whether to set the - * withCredentials XHR flag for AJAX requests (when loading tile sources). - * Inherited from the viewer if not set. - * @param {Boolean} options.useCanvas Set to false to prevent any use of - * the canvas API. Inherited from the viewer if not set. + * domains. + * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set + * the withCredentials XHR flag for AJAX requests (when loading tile sources). + * @param {Boolean} [options.useCanvas=true] Set to false to prevent any use + * of the canvas API. */ $.ImageTileSource = function (options) { options = $.extend({ - buildPyramid: true + buildPyramid: true, + crossOriginPolicy: false, + ajaxWithCredentials: false, + useCanvas: true }, options); $.TileSource.apply(this, [options]); diff --git a/src/viewer.js b/src/viewer.js index b0e9381f..f4bb8dc4 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2040,6 +2040,22 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, } } + function waitUntilReady(tileSource, originalTileSource) { + if (tileSource.ready) { + successCallback(tileSource); + } else { + tileSource.addHandler('ready', function () { + successCallback(tileSource); + }); + tileSource.addHandler('open-failed', function (event) { + failCallback({ + message: event.message, + source: originalTileSource + }); + }); + } + } + setTimeout( function() { if ( $.type( tileSource ) == 'string' ) { //If its still a string it means it must be a url at this point @@ -2083,24 +2099,11 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, return; } var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] ); - var readySource = new $TileSource(options); - if (readySource.ready) { - successCallback(readySource); - } else { - readySource.addHandler('ready', function () { - successCallback(readySource); - }); - readySource.addHandler('open-failed', function (event) { - failCallback({ - message: event.message, - source: tileSource - }); - }); - } + waitUntilReady(new $TileSource(options), tileSource); } } else { //can assume it's already a tile source implementation - successCallback( tileSource ); + waitUntilReady(tileSource, tileSource); } }); } From f58a525f47fa5178af087d5bafbc5c6dca541aaa Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 5 Nov 2015 22:16:20 -0500 Subject: [PATCH 11/15] Discard the image as soon as possible. --- src/imagetilesource.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index b058dfc0..a0650be2 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -103,7 +103,7 @@ * @throws {Error} */ getImageInfo: function (url) { - var image = new Image(); + var image = this.image = new Image(); var _this = this; if (this.crossOriginPolicy) { @@ -122,7 +122,7 @@ _this._tileHeight = _this.height; _this.tileOverlap = 0; _this.minLevel = 0; - _this.levels = _this._buildLevels(image); + _this.levels = _this._buildLevels(); _this.maxLevel = _this.levels.length - 1; _this.ready = true; @@ -226,30 +226,34 @@ * @private Build the differents levels of the pyramid if possible * (canvas API enabled and no canvas tainting issue) */ - _buildLevels: function (image) { + _buildLevels: function () { var levels = [{ - url: image.src, - width: image.naturalWidth, - height: image.naturalHeight + url: this.image.src, + width: this.image.naturalWidth, + height: this.image.naturalHeight }]; if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { + // We don't need the image anymore. Allows it to be GC. + delete this.image; return levels; } - var currentWidth = image.naturalWidth; - var currentHeight = image.naturalHeight; + var currentWidth = this.image.naturalWidth; + var currentHeight = this.image.naturalHeight; var bigCanvas = document.createElement("canvas"); var bigContext = bigCanvas.getContext("2d"); bigCanvas.width = currentWidth; bigCanvas.height = currentHeight; - bigContext.drawImage(image, 0, 0, currentWidth, currentHeight); + bigContext.drawImage(this.image, 0, 0, currentWidth, currentHeight); // We cache the context of the highest level because the browser // is a lot faster at downsampling something it already has // downsampled before. levels[0].context2D = bigContext; + // We don't need the image anymore. Allows it to be GC. + delete this.image; if ($.isCanvasTainted(bigCanvas)) { // If the canvas is tainted, we can't compute the pyramid. From 760aaa7dcaa4070924dc5c00bf26e4cc2d4e3235 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 5 Nov 2015 22:32:56 -0500 Subject: [PATCH 12/15] Fix tests. --- src/viewer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/viewer.js b/src/viewer.js index f4bb8dc4..93235038 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2101,6 +2101,8 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] ); waitUntilReady(new $TileSource(options), tileSource); } + } else if ($.isArray(tileSource)) { + successCallback(tileSource); } else { //can assume it's already a tile source implementation waitUntilReady(tileSource, tileSource); From 954cbbdc46a6a2a8e380353d9c33bb0689047214 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 9 Nov 2015 17:57:39 -0500 Subject: [PATCH 13/15] Rename this.image to this._image --- src/imagetilesource.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/imagetilesource.js b/src/imagetilesource.js index a0650be2..98f6c500 100644 --- a/src/imagetilesource.js +++ b/src/imagetilesource.js @@ -103,7 +103,7 @@ * @throws {Error} */ getImageInfo: function (url) { - var image = this.image = new Image(); + var image = this._image = new Image(); var _this = this; if (this.crossOriginPolicy) { @@ -228,32 +228,32 @@ */ _buildLevels: function () { var levels = [{ - url: this.image.src, - width: this.image.naturalWidth, - height: this.image.naturalHeight + url: this._image.src, + width: this._image.naturalWidth, + height: this._image.naturalHeight }]; if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { // We don't need the image anymore. Allows it to be GC. - delete this.image; + delete this._image; return levels; } - var currentWidth = this.image.naturalWidth; - var currentHeight = this.image.naturalHeight; + var currentWidth = this._image.naturalWidth; + var currentHeight = this._image.naturalHeight; var bigCanvas = document.createElement("canvas"); var bigContext = bigCanvas.getContext("2d"); bigCanvas.width = currentWidth; bigCanvas.height = currentHeight; - bigContext.drawImage(this.image, 0, 0, currentWidth, currentHeight); + bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight); // We cache the context of the highest level because the browser // is a lot faster at downsampling something it already has // downsampled before. levels[0].context2D = bigContext; // We don't need the image anymore. Allows it to be GC. - delete this.image; + delete this._image; if ($.isCanvasTainted(bigCanvas)) { // If the canvas is tainted, we can't compute the pyramid. From 7c62974e57ee12ffafa22379b416ff6a45a47ce0 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 9 Nov 2015 18:19:17 -0500 Subject: [PATCH 14/15] Exit early if an array is provided as a tile source. --- src/viewer.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index 93235038..5dbd814c 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1286,19 +1286,21 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, } } + if ($.isArray(options.tileSource)) { + setTimeout(function() { + raiseAddItemFailed({ + message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.", + source: options.tileSource, + options: options + }); + }); + return; + } + this._loadQueue.push(myQueueItem); getTileSourceImplementation( this, options.tileSource, function( tileSource ) { - if ( tileSource instanceof Array ) { - raiseAddItemFailed({ - message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.", - source: tileSource, - options: options - }); - return; - } - myQueueItem.tileSource = tileSource; // add everybody at the front of the queue that's ready to go @@ -2101,8 +2103,6 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] ); waitUntilReady(new $TileSource(options), tileSource); } - } else if ($.isArray(tileSource)) { - successCallback(tileSource); } else { //can assume it's already a tile source implementation waitUntilReady(tileSource, tileSource); From f4f30c29251dcb39f72d7120d4641a25013b7d2e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 9 Nov 2015 19:02:31 -0500 Subject: [PATCH 15/15] Fix race condition in navigator tests. --- test/modules/navigator.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/modules/navigator.js b/test/modules/navigator.js index c6381a20..e300b4a0 100644 --- a/test/modules/navigator.js +++ b/test/modules/navigator.js @@ -801,7 +801,6 @@ }); asyncTest('Item positions including collection mode', function() { - var navAddCount = 0; viewer = OpenSeadragon({ id: 'example', @@ -815,16 +814,16 @@ var openHandler = function() { viewer.removeHandler('open', openHandler); viewer.navigator.world.addHandler('add-item', navOpenHandler); + // The navigator may already have added the items. + navOpenHandler(); }; var navOpenHandler = function(event) { - navAddCount++; - if (navAddCount === 2) { + if (viewer.navigator.world.getItemCount() === 2) { viewer.navigator.world.removeHandler('add-item', navOpenHandler); setTimeout(function() { // Test initial formation - equal(viewer.navigator.world.getItemCount(), 2, 'navigator has both items'); for (var i = 0; i < 2; i++) { propEqual(viewer.navigator.world.getItemAt(i).getBounds(), viewer.world.getItemAt(i).getBounds(), 'bounds are the same');