/* * OpenSeadragon - ImageLoader * * 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($){ /** * @private * @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 {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} [options.crossOriginPolicy] - CORS policy to use for downloads * @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. */ function ImageJob (options) { $.extend(true, this, { timeout: $.DEFAULT_SETTINGS.timeout, jobId: null }, options); /** * Image object which will contain downloaded image. * @member {Image} image * @memberof OpenSeadragon.ImageJob# */ this.image = null; } ImageJob.prototype = { errorMsg: null, /** * Starts the image job. * @method */ start: function(){ var self = this; var selfAbort = this.abort; this.image = new Image(); this.image.onload = function(){ self.finish(true); }; this.image.onabort = this.image.onerror = function() { self.errorMsg = "Image load aborted"; self.finish(false); }; this.jobId = window.setTimeout(function(){ self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)"; self.finish(false); }, this.timeout); // 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 (this.loadWithAjax) { this.request = $.makeAjaxRequest({ url: this.src, withCredentials: this.ajaxWithCredentials, headers: this.ajaxHeaders, responseType: "arraybuffer", success: function(request) { var blb; // Make the raw data into a blob. // BlobBuilder fallback adapted from // http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility try { blb = new window.Blob([request.response]); } catch (e) { var BlobBuilder = ( window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder ); if (e.name === 'TypeError' && BlobBuilder) { var bb = new BlobBuilder(); bb.append(request.response); blb = bb.getBlob(); } } // If the blob is empty for some reason consider the image load a failure. if (blb.size === 0) { self.errorMsg = "Empty image response."; self.finish(false); } // 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. var url = (window.URL || window.webkitURL).createObjectURL(blb); self.image.src = url; }, error: function(request) { self.errorMsg = "Image load aborted - XHR error"; self.finish(false); } }); // Provide a function to properly abort the request. this.abort = function() { self.request.abort(); // Call the existing abort function if available if (typeof selfAbort === "function") { selfAbort(); } }; } else { if (this.crossOriginPolicy !== false) { this.image.crossOrigin = this.crossOriginPolicy; } this.image.src = this.src; } }, finish: function(successful) { this.image.onload = this.image.onerror = this.image.onabort = null; if (!successful) { this.image = null; } 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 {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 {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) { var _this = this, complete = function(job) { completeJob(_this, job, options.callback); }, jobOptions = { src: options.src, loadWithAjax: options.loadWithAjax, ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null, crossOriginPolicy: options.crossOriginPolicy, ajaxWithCredentials: options.ajaxWithCredentials, 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.image, job.errorMsg, job.request); } }(OpenSeadragon));