openseadragon/src/imageloader.js

245 lines
8.9 KiB
JavaScript

/*
* OpenSeadragon - ImageLoader
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2022 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 ImageJob
* @classdesc Handles downloading of a single image.
* @param {Object} options - Options for this ImageJob.
* @param {String} [options.src] - URL of image to download.
* @param {Tile} [options.tile] - Tile that belongs the data to.
* @param {TileSource} [options.source] - Image loading strategy
* @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
* @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
* @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX requests.
* @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {String} [options.postData] - HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* @param {Function} [options.callback] - Called once image has been downloaded.
* @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.
*/
$.ImageJob = function(options) {
$.extend(true, this, {
timeout: $.DEFAULT_SETTINGS.timeout,
jobId: null
}, options);
/**
* Data object which will contain downloaded image data.
* @member {Image|*} image data object, by default an Image object (depends on TileSource)
* @memberof OpenSeadragon.ImageJob#
*/
this.data = null;
/**
* User workspace to populate with helper variables
* @member {*} userData to append custom data and avoid namespace collision
* @memberof OpenSeadragon.ImageJob#
*/
this.userData = {};
/**
* Error message holder
* @member {string} error message
* @memberof OpenSeadragon.ImageJob#
* @private
*/
this.errorMsg = null;
};
$.ImageJob.prototype = {
/**
* Starts the image job.
* @method
*/
start: function() {
var self = this;
var selfAbort = this.abort;
this.jobId = window.setTimeout(function () {
self.finish(null, null, "Image load exceeded timeout (" + self.timeout + " ms)");
}, this.timeout);
this.abort = function() {
self.source.downloadTileAbort(self);
if (typeof selfAbort === "function") {
selfAbort();
}
};
this.source.downloadTileStart(this);
},
/**
* Finish this job.
* @param {*} data data that has been downloaded
* @param {XMLHttpRequest} request reference to the request if used
* @param {string} errorMessage description upon failure
*/
finish: function(data, request, errorMessage ) {
this.data = data;
this.request = request;
this.errorMsg = errorMessage;
if (this.jobId) {
window.clearTimeout(this.jobId);
}
this.callback(this);
}
};
/**
* @class ImageLoader
* @memberof OpenSeadragon
* @classdesc Handles downloading of a set of images using asynchronous queue pattern.
* You generally won't have to interact with the ImageLoader directly.
* @param {Object} options - Options for this ImageLoader.
* @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
* @param {Number} [options.timeout] - The max number of milliseconds that an image job may take to complete.
*/
$.ImageLoader = function(options) {
$.extend(true, this, {
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
timeout: $.DEFAULT_SETTINGS.timeout,
jobQueue: [],
jobsInProgress: 0
}, options);
};
/** @lends OpenSeadragon.ImageLoader.prototype */
$.ImageLoader.prototype = {
/**
* Add an unloaded image to the loader queue.
* @method
* @param {Object} options - Options for this job.
* @param {String} [options.src] - URL of image to download.
* @param {Tile} [options.tile] - Tile that belongs the data to. The tile instance
* is not internally used and serves for custom TileSources implementations.
* @param {TileSource} [options.source] - Image loading strategy
* @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
* @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
* @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {String} [options.postData] - POST parameters (usually but not necessarily in k=v&k2=v2... form,
* see TileSource::getPostData) or null
* @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
* requests.
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
*/
addJob: function(options) {
if (!options.source) {
$.console.error('ImageLoader.prototype.addJob() requires [options.source]. ' +
'TileSource since new API defines how images are fetched. Creating a dummy TileSource.');
var implementation = $.TileSource.prototype;
options.source = {
downloadTileStart: implementation.downloadTileStart,
downloadTileAbort: implementation.downloadTileAbort
};
}
var _this = this,
complete = function(job) {
completeJob(_this, job, options.callback);
},
jobOptions = {
src: options.src,
tile: options.tile || {},
source: options.source,
loadWithAjax: options.loadWithAjax,
ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null,
crossOriginPolicy: options.crossOriginPolicy,
ajaxWithCredentials: options.ajaxWithCredentials,
postData: options.postData,
callback: complete,
abort: options.abort,
timeout: this.timeout
},
newJob = new $.ImageJob(jobOptions);
if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
newJob.start();
this.jobsInProgress++;
}
else {
this.jobQueue.push( newJob );
}
},
/**
* Clear any unstarted image loading jobs from the queue.
* @method
*/
clear: function() {
for( var i = 0; i < this.jobQueue.length; i++ ) {
var job = this.jobQueue[i];
if ( typeof job.abort === "function" ) {
job.abort();
}
}
this.jobQueue = [];
}
};
/**
* Cleans up ImageJob once completed.
* @method
* @private
* @param loader - ImageLoader used to start job.
* @param job - The ImageJob that has completed.
* @param callback - Called once cleanup is finished.
*/
function completeJob(loader, job, callback) {
var nextJob;
loader.jobsInProgress--;
if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
nextJob = loader.jobQueue.shift();
nextJob.start();
loader.jobsInProgress++;
}
callback(job.data, job.errorMsg, job.request);
}
}(OpenSeadragon));