diff --git a/src/imageloader.js b/src/imageloader.js index 14e34f33..c89850b7 100644 --- a/src/imageloader.js +++ b/src/imageloader.js @@ -35,7 +35,6 @@ (function($){ /** - * @private * @class ImageJob * @classdesc Handles downloading of a single image. * @param {Object} options - Options for this ImageJob. @@ -50,7 +49,7 @@ * @param {Function} [options.abort] - Called when this image job is aborted. * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete. */ -function ImageJob (options) { +$.ImageJob = function(options) { $.extend(true, this, { timeout: $.DEFAULT_SETTINGS.timeout, @@ -63,9 +62,16 @@ function ImageJob (options) { * @memberof OpenSeadragon.ImageJob# */ this.data = null; -} -ImageJob.prototype = { + /** + * User workspace to populate with helper variables + * @member {*} user data, for people to append their data + * @memberof OpenSeadragon.ImageJob# + */ + this.userData = {}; +}; + +$.ImageJob.prototype = { errorMsg: null, /** @@ -100,7 +106,6 @@ ImageJob.prototype = { this.callback(this); } - }; /** @@ -146,7 +151,7 @@ $.ImageLoader.prototype = { if (!options.source) { $.console.error('ImageLoader.prototype.addJob() requires [options.source]. ' + 'TileSource since new API defines how images are fetched. Creating a dummy TileSource.'); - let implementation = $.TileSource.prototype; + var implementation = $.TileSource.prototype; options.source = { downloadTileStart: implementation.downloadTileStart, downloadTileAbort: implementation.downloadTileAbort, @@ -170,7 +175,7 @@ $.ImageLoader.prototype = { abort: options.abort, timeout: this.timeout }, - newJob = new ImageJob(jobOptions); + newJob = new $.ImageJob(jobOptions); if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) { newJob.start(); diff --git a/src/tile.js b/src/tile.js index 0f5aaa60..492d4d70 100644 --- a/src/tile.js +++ b/src/tile.js @@ -298,7 +298,9 @@ $.Tile.prototype = { if ( !this.element ) { var image = this.image; - if (!this.image) return; + if (!this.image) { + return; + } this.element = $.makeNeutralElement( "div" ); this.imgElement = image.cloneNode(); @@ -335,7 +337,7 @@ $.Tile.prototype = { * @return {Image} */ get image() { - this.cacheImageRecord.getImage(); + return this.cacheImageRecord.getImage(); }, /** @@ -367,8 +369,7 @@ $.Tile.prototype = { var position = this.position.times($.pixelDensityRatio), size = this.size.times($.pixelDensityRatio), - rendered, - hasTransparency; + rendered; if (!this.context2D && !this.cacheImageRecord) { $.console.warn( diff --git a/src/tilecache.js b/src/tilecache.js index 588a8636..8758017e 100644 --- a/src/tilecache.js +++ b/src/tilecache.js @@ -139,8 +139,8 @@ $.TileCache.prototype = { if (!imageRecord) { if (!options.data) { - $.console.error("[TileCache.cacheTile] options.image was renamed to options.data. '.image' attribute " - + "has been deprecated and will be removed in the future."); + $.console.error("[TileCache.cacheTile] options.image was renamed to options.data. '.image' attribute " + + "has been deprecated and will be removed in the future."); options.data = options.image; } diff --git a/src/tiledimage.js b/src/tiledimage.js index 0d44f383..46ac109d 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1649,7 +1649,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @private * @inner * @param {OpenSeadragon.Tile} tile - * @param {*} data image data + * @param {*} data image data, the output of TileSource.prototype.downloadTileFinish(), by default Image object * @param {Number || undefined} cutoff * @param {XMLHttpRequest || undefined} tileRequest */ @@ -1667,7 +1667,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag if (increment === 0) { tile.loading = false; tile.loaded = true; - tile.hasTransparency = this.source.hasTransparency( + tile.hasTransparency = _this.source.hasTransparency( tile.context2D, tile.url, tile.ajaxHeaders, tile.postData ); if (!tile.context2D) { @@ -1689,8 +1689,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @event tile-loaded * @memberof OpenSeadragon.Viewer * @type {object} - * @property {Image|*} image - The image (data) of the tile. - * @property {*} data - image data + * @property {Image || *} image - The image (data) of the tile. Deprecated. + * @property {*} data - image data, the output of TileSource.prototype.downloadTileFinish(), + * by default Image object * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile. * @property {OpenSeadragon.Tile} tile - The tile which has been loaded. * @property {XMLHttpRequest} tileRequest - The AJAX request that loaded this tile (if applicable). @@ -1704,7 +1705,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag tiledImage: this, tileRequest: tileRequest, get image() { - $.console.error("[tile-loaded] event property 'image' has been deprecated and will be removed in the future"); + $.console.error("[tile-loaded] event property 'image' has been deprecated and will be removed."); return data; }, data: data, diff --git a/src/tilesource.js b/src/tilesource.js index 4dd22066..93bd00b8 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -720,33 +720,38 @@ $.TileSource.prototype = { }, /** - * Download tile data - * @param {object} context job context that you have to call finish(...) on. It also contains abort(...) function - * that can be called to abort the job. + * Download tile data. + * Note that if you override any of downloadTile*() functions, you should override all of them. + * @param {ImageJob} context job context that you have to call finish(...) on. * @param {String} [context.src] - URL of image to download. * @param {String} [context.loadWithAjax] - Whether to load this image with AJAX. * @param {String} [context.ajaxHeaders] - Headers to add to the image request if using AJAX. * @param {String} [context.crossOriginPolicy] - CORS policy to use for downloads * @param {String} [context.postData] - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, * see TileSrouce::getPostData) or null - * @param {Function} [context.callback] - Called once image has been downloaded. + * + * @param {*} [context.userData] - Empty object to attach your own data and helper variables to. + * @param {Function} [context.callback] - Called automatically once image has been downloaded (triggered by finish). + * @param {Function} [context.finish] - Should be called unless abort() was executed, e.g. on all occasions, + * be it successful or unsuccessful request. * @param {Function} [context.abort] - Called when this image job is aborted. * @param {Number} [context.timeout] - The max number of milliseconds that this image job may take to complete. */ downloadTileStart: function (context) { - context.image = new Image(); + var dataStore = context.userData; + dataStore.image = new Image(); - context.image.onload = function(){ + dataStore.image.onload = function(){ context.finish(true); }; - context.image.onabort = context.image.onerror = function() { + dataStore.image.onabort = dataStore.image.onerror = function() { context.finish(false, "Image load aborted"); }; // Load the tile with an AJAX request if the loadWithAjax option is // set. Otherwise load the image by setting the source proprety of the image object. if (context.loadWithAjax) { - context.request = $.makeAjaxRequest({ + dataStore.request = $.makeAjaxRequest({ url: context.src, withCredentials: context.ajaxWithCredentials, headers: context.ajaxHeaders, @@ -778,7 +783,7 @@ $.TileSource.prototype = { } else { // Create a URL for the blob data and make it the source of the image object. // This will still trigger Image.onload to indicate a successful tile load. - context.image.src = (window.URL || window.webkitURL).createObjectURL(blb); + dataStore.image.src = (window.URL || window.webkitURL).createObjectURL(blb); } }, error: function(request) { @@ -787,50 +792,59 @@ $.TileSource.prototype = { }); } else { if (context.crossOriginPolicy !== false) { - context.image.crossOrigin = context.crossOriginPolicy; + dataStore.image.crossOrigin = context.crossOriginPolicy; } - - context.image.src = context.src; + dataStore.image.src = context.src; } }, /** * Provide means of aborting the execution. - * @param {object} context job, the same object as with downloadTileStart(..) + * Note that if you override any of downloadTile*() functions, you should override all of them. + * @param {ImageJob} context job, the same object as with downloadTileStart(..) + * @param {*} [context.userData] - Empty object to attach (and mainly read) your own data. */ downloadTileAbort: function (context) { - context.request.abort(); + context.userData.request.abort(); }, /** - * @param {object} context job, the same object as with downloadTileStart(..) + * Note that if you override any of downloadTile*() functions, you should override all of them, + * unless you just want to for example here change the format of the data. + * Note that unless this function returns Image object, you should also override *TileCache() functions + * to re-define how the data is being cached. + * @param {ImageJob} context job, the same object as with downloadTileStart(..) + * @param {*} [context.userData] - Empty object to attach (and mainly read) your own data. * @param successful true if successful - * @return {null|*} null to indicate missing data or data object - * for example, can return default value if the request was unsuccessful such as default error image + * @return {* || null} tile data in a format you want to have in the system, or null to indicate missing data + * also for example, a default value (white image? error image?) can be returned if the request was unsuccessful */ downloadTileFinish: function (context, successful) { - if (!context.image) { + var image = context.userData.image; + if (!image) { return null; } - context.image.onload = context.image.onerror = context.image.onabort = null; - if (!successful) { - return null; - } - return context.image; + image.onload = image.onerror = image.onabort = null; + return successful ? image : null; }, /** - * Create cache object from the result of the download process - * @param {Tile} tile instance the cache was created with + * Create cache object from the result of the download process. The + * cacheObject parameter should be used to attach the data to, there are no + * conventions on how it should be stored - all the logic is implemented within *TileCache() functions. + * + * Note that if you override any of *TileCache() functions, you should override all of them. * @param {object} cacheObject context cache object * @param {*} data the result of downloadTileFinish() function + * @param {Tile} tile instance the cache was created with */ createTileCache: function(cacheObject, data, tile) { cacheObject._data = data; }, /** - * Cache object destructor, unset all properties to allow GC collection. + * Cache object destructor, unset all properties you created to allow GC collection. + * Note that if you override any of *TileCache() functions, you should override all of them. * @param {object} cacheObject context cache object */ destroyTileCache: function (cacheObject) { @@ -840,6 +854,7 @@ $.TileSource.prototype = { /** * Raw data getter + * Note that if you override any of *TileCache() functions, you should override all of them. * @param {object} cacheObject context cache object * @return {*} cache data */ @@ -851,6 +866,7 @@ $.TileSource.prototype = { * Compatibility image element getter * - plugins might need image representation of the data * - div HTML rendering relies on image element presence + * Note that if you override any of *TileCache() functions, you should override all of them. * @param {object} cacheObject context cache object * @return {Image} cache data as an Image */ @@ -862,6 +878,7 @@ $.TileSource.prototype = { * Compatibility context 2D getter * - most heavily used rendering method is a canvas-based approach, * convert the data to a canvas and return it's 2D context + * Note that if you override any of *TileCache() functions, you should override all of them. * @param {object} cacheObject context cache object * @return {CanvasRenderingContext2D} context of the canvas representation of the cache data */ diff --git a/test/modules/tilecache.js b/test/modules/tilecache.js index b933f3d1..26ee51ba 100644 --- a/test/modules/tilecache.js +++ b/test/modules/tilecache.js @@ -2,35 +2,6 @@ (function() { - var tileSourceCacheAPI = { - createTileCache: function(cacheObject, data, tile) { - cacheObject._data = data; - }, - destroyTileCache: function (cacheObject) { - cacheObject._data = null; - cacheObject._renderedContext = null; - }, - getTileCacheData: function(cacheObject) { - return cacheObject._data; - }, - getTileCacheDataAsImage: function(cacheObject) { - return cacheObject._data; //the data itself by default is Image - }, - getTileCacheDataAsContext2D: function(cacheObject) { - if (!cacheObject._renderedContext) { - var canvas = document.createElement( 'canvas' ); - canvas.width = cacheObject._data.width; - canvas.height = cacheObject._data.height; - cacheObject._renderedContext = canvas.getContext('2d'); - cacheObject._renderedContext.drawImage( cacheObject._data, 0, 0 ); - //since we are caching the prerendered image on a canvas - //allow the image to not be held in memory - cacheObject._data = null; - } - return cacheObject._renderedContext; - } - }; - // ---------- QUnit.module('TileCache', { beforeEach: function () { @@ -49,11 +20,11 @@ }; var fakeTiledImage0 = { viewer: fakeViewer, - source: tileSourceCacheAPI + source: OpenSeadragon.TileSource.prototype }; var fakeTiledImage1 = { viewer: fakeViewer, - source: tileSourceCacheAPI + source: OpenSeadragon.TileSource.prototype }; var fakeTile0 = { @@ -106,7 +77,7 @@ }; var fakeTiledImage0 = { viewer: fakeViewer, - source: tileSourceCacheAPI + source: OpenSeadragon.TileSource.prototype }; var fakeTile0 = {