mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-12-01 09:36:08 +03:00
Merge pull request #402 from rdlester/master
Added ImageLoader; loads batches of images using async queue pattern
This commit is contained in:
commit
975828c057
23
Gruntfile.js
23
Gruntfile.js
@ -45,6 +45,7 @@ module.exports = function(grunt) {
|
|||||||
"src/referencestrip.js",
|
"src/referencestrip.js",
|
||||||
"src/displayrectangle.js",
|
"src/displayrectangle.js",
|
||||||
"src/spring.js",
|
"src/spring.js",
|
||||||
|
"src/imageLoader.js",
|
||||||
"src/tile.js",
|
"src/tile.js",
|
||||||
"src/overlay.js",
|
"src/overlay.js",
|
||||||
"src/drawer.js",
|
"src/drawer.js",
|
||||||
@ -73,11 +74,11 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
concat: {
|
concat: {
|
||||||
options: {
|
options: {
|
||||||
banner: "//! <%= pkg.name %> <%= pkg.version %>\n"
|
banner: "//! <%= pkg.name %> <%= pkg.version %>\n" +
|
||||||
+ "//! Built on <%= grunt.template.today('yyyy-mm-dd') %>\n"
|
"//! Built on <%= grunt.template.today('yyyy-mm-dd') %>\n" +
|
||||||
+ "//! Git commit: <%= gitInfo %>\n"
|
"//! Git commit: <%= gitInfo %>\n" +
|
||||||
+ "//! http://openseadragon.github.io\n"
|
"//! http://openseadragon.github.io\n" +
|
||||||
+ "//! License: http://openseadragon.github.io/license/\n\n",
|
"//! License: http://openseadragon.github.io/license/\n\n",
|
||||||
process: true
|
process: true
|
||||||
},
|
},
|
||||||
dist: {
|
dist: {
|
||||||
@ -182,9 +183,9 @@ module.exports = function(grunt) {
|
|||||||
// Creates a directory tree to be compressed into a package.
|
// Creates a directory tree to be compressed into a package.
|
||||||
grunt.registerTask("copy:package", function() {
|
grunt.registerTask("copy:package", function() {
|
||||||
grunt.file.recurse("build/openseadragon", function(abspath, rootdir, subdir, filename) {
|
grunt.file.recurse("build/openseadragon", function(abspath, rootdir, subdir, filename) {
|
||||||
var dest = packageDir
|
var dest = packageDir +
|
||||||
+ (subdir ? subdir + "/" : '/')
|
(subdir ? subdir + "/" : '/') +
|
||||||
+ filename;
|
filename;
|
||||||
grunt.file.copy(abspath, dest);
|
grunt.file.copy(abspath, dest);
|
||||||
});
|
});
|
||||||
grunt.file.copy("changelog.txt", packageDir + "changelog.txt");
|
grunt.file.copy("changelog.txt", packageDir + "changelog.txt");
|
||||||
@ -200,9 +201,9 @@ module.exports = function(grunt) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest = releaseRoot
|
var dest = releaseRoot +
|
||||||
+ (subdir ? subdir + "/" : '/')
|
(subdir ? subdir + "/" : '/') +
|
||||||
+ filename;
|
filename;
|
||||||
|
|
||||||
grunt.file.copy(abspath, dest);
|
grunt.file.copy(abspath, dest);
|
||||||
});
|
});
|
||||||
|
120
src/drawer.js
120
src/drawer.js
@ -76,7 +76,7 @@ $.Drawer = function( options ) {
|
|||||||
|
|
||||||
//internal state properties
|
//internal state properties
|
||||||
viewer: null,
|
viewer: null,
|
||||||
downloading: 0, // How many images are currently being loaded in parallel.
|
imageLoader: new $.ImageLoader(),
|
||||||
tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
|
tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
|
||||||
tilesLoaded: [], // An unordered list of Tiles with loaded images.
|
tilesLoaded: [], // An unordered list of Tiles with loaded images.
|
||||||
coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
|
coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
|
||||||
@ -92,7 +92,6 @@ $.Drawer = function( options ) {
|
|||||||
//configurable settings
|
//configurable settings
|
||||||
opacity: $.DEFAULT_SETTINGS.opacity,
|
opacity: $.DEFAULT_SETTINGS.opacity,
|
||||||
maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
|
maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
|
||||||
imageLoaderLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
|
|
||||||
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
|
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
|
||||||
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
|
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
|
||||||
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
|
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
|
||||||
@ -296,77 +295,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Used internally to load images when required. May also be used to
|
|
||||||
* preload a set of images so the browser will have them available in
|
|
||||||
* the local cache to optimize user experience in certain cases. Because
|
|
||||||
* the number of parallel image loads is configurable, if too many images
|
|
||||||
* are currently being loaded, the request will be ignored. Since by
|
|
||||||
* default drawer.imageLoaderLimit is 0, the native browser parallel
|
|
||||||
* image loading policy will be used.
|
|
||||||
* @method
|
|
||||||
* @param {String} src - The url of the image to load.
|
|
||||||
* @param {Function} callback - The function that will be called with the
|
|
||||||
* Image object as the only parameter if it was loaded successfully.
|
|
||||||
* If an error occured, or the request timed out or was aborted,
|
|
||||||
* the parameter is null instead.
|
|
||||||
* @return {Boolean} loading - Whether the request was submitted or ignored
|
|
||||||
* based on OpenSeadragon.DEFAULT_SETTINGS.imageLoaderLimit.
|
|
||||||
*/
|
|
||||||
loadImage: function( src, callback ) {
|
|
||||||
var _this = this,
|
|
||||||
loading = false,
|
|
||||||
image,
|
|
||||||
jobid,
|
|
||||||
complete;
|
|
||||||
|
|
||||||
if ( !this.imageLoaderLimit ||
|
|
||||||
this.downloading < this.imageLoaderLimit ) {
|
|
||||||
|
|
||||||
this.downloading++;
|
|
||||||
|
|
||||||
image = new Image();
|
|
||||||
|
|
||||||
if ( _this.crossOriginPolicy !== false ) {
|
|
||||||
image.crossOrigin = _this.crossOriginPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
complete = function( imagesrc, resultingImage ){
|
|
||||||
_this.downloading--;
|
|
||||||
if (typeof ( callback ) == "function") {
|
|
||||||
try {
|
|
||||||
callback( resultingImage );
|
|
||||||
} catch ( e ) {
|
|
||||||
$.console.error(
|
|
||||||
"%s while executing %s callback: %s",
|
|
||||||
e.name,
|
|
||||||
src,
|
|
||||||
e.message,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
image.onload = function(){
|
|
||||||
finishLoadingImage( image, complete, true, jobid );
|
|
||||||
};
|
|
||||||
|
|
||||||
image.onabort = image.onerror = function(){
|
|
||||||
finishLoadingImage( image, complete, false, jobid );
|
|
||||||
};
|
|
||||||
|
|
||||||
jobid = window.setTimeout( function(){
|
|
||||||
finishLoadingImage( image, complete, false, jobid );
|
|
||||||
}, this.timeout );
|
|
||||||
|
|
||||||
loading = true;
|
|
||||||
image.src = src;
|
|
||||||
}
|
|
||||||
|
|
||||||
return loading;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether rotation is supported or not.
|
* Returns whether rotation is supported or not.
|
||||||
* @method
|
* @method
|
||||||
@ -436,13 +364,13 @@ function updateViewport( drawer ) {
|
|||||||
levelOpacity,
|
levelOpacity,
|
||||||
levelVisibility;
|
levelVisibility;
|
||||||
|
|
||||||
//TODO
|
// Reset tile's internal drawn state
|
||||||
while ( drawer.lastDrawn.length > 0 ) {
|
while ( drawer.lastDrawn.length > 0 ) {
|
||||||
tile = drawer.lastDrawn.pop();
|
tile = drawer.lastDrawn.pop();
|
||||||
tile.beingDrawn = false;
|
tile.beingDrawn = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
// Clear canvas
|
||||||
drawer.canvas.innerHTML = "";
|
drawer.canvas.innerHTML = "";
|
||||||
if ( drawer.useCanvas ) {
|
if ( drawer.useCanvas ) {
|
||||||
if( drawer.canvas.width != viewportSize.x ||
|
if( drawer.canvas.width != viewportSize.x ||
|
||||||
@ -470,7 +398,7 @@ function updateViewport( drawer ) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
// Calculate viewport rect / bounds
|
||||||
if ( !drawer.wrapHorizontal ) {
|
if ( !drawer.wrapHorizontal ) {
|
||||||
viewportTL.x = Math.max( viewportTL.x, 0 );
|
viewportTL.x = Math.max( viewportTL.x, 0 );
|
||||||
viewportBR.x = Math.min( viewportBR.x, 1 );
|
viewportBR.x = Math.min( viewportBR.x, 1 );
|
||||||
@ -480,10 +408,12 @@ function updateViewport( drawer ) {
|
|||||||
viewportBR.y = Math.min( viewportBR.y, drawer.normHeight );
|
viewportBR.y = Math.min( viewportBR.y, drawer.normHeight );
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
// Calculations for the interval of levels to draw
|
||||||
|
// (above in initial var statement)
|
||||||
|
// can return invalid intervals; fix that here if necessary
|
||||||
lowestLevel = Math.min( lowestLevel, highestLevel );
|
lowestLevel = Math.min( lowestLevel, highestLevel );
|
||||||
|
|
||||||
//TODO
|
// Update any level that will be drawn
|
||||||
var drawLevel; // FIXME: drawLevel should have a more explanatory name
|
var drawLevel; // FIXME: drawLevel should have a more explanatory name
|
||||||
for ( level = highestLevel; level >= lowestLevel; level-- ) {
|
for ( level = highestLevel; level >= lowestLevel; level-- ) {
|
||||||
drawLevel = false;
|
drawLevel = false;
|
||||||
@ -528,7 +458,7 @@ function updateViewport( drawer ) {
|
|||||||
optimalRatio - renderPixelRatioT
|
optimalRatio - renderPixelRatioT
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO
|
// Update the level and keep track of 'best' tile to load
|
||||||
best = updateLevel(
|
best = updateLevel(
|
||||||
drawer,
|
drawer,
|
||||||
haveDrawn,
|
haveDrawn,
|
||||||
@ -542,16 +472,17 @@ function updateViewport( drawer ) {
|
|||||||
best
|
best
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO
|
// Stop the loop if lower-res tiles would all be covered by
|
||||||
|
// already drawn tiles
|
||||||
if ( providesCoverage( drawer.coverage, level ) ) {
|
if ( providesCoverage( drawer.coverage, level ) ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
// Perform the actual drawing
|
||||||
drawTiles( drawer, drawer.lastDrawn );
|
drawTiles( drawer, drawer.lastDrawn );
|
||||||
|
|
||||||
//TODO
|
// Load the new 'best' tile
|
||||||
if ( best ) {
|
if ( best ) {
|
||||||
loadTile( drawer, best, currentTime );
|
loadTile( drawer, best, currentTime );
|
||||||
// because we haven't finished drawing, so
|
// because we haven't finished drawing, so
|
||||||
@ -756,18 +687,18 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, normHeig
|
|||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function loadTile( drawer, tile, time ) {
|
function loadTile( drawer, tile, time ) {
|
||||||
if( drawer.viewport.collectionMode ){
|
if( drawer.viewport.collectionMode ){
|
||||||
drawer.midUpdate = false;
|
drawer.midUpdate = false;
|
||||||
onTileLoad( drawer, tile, time );
|
onTileLoad( drawer, tile, time );
|
||||||
} else {
|
} else {
|
||||||
tile.loading = drawer.loadImage(
|
tile.loading = true;
|
||||||
tile.url,
|
drawer.imageLoader.addJob({
|
||||||
function( image ){
|
src: tile.url,
|
||||||
|
callback: function( image ){
|
||||||
onTileLoad( drawer, tile, time, image );
|
onTileLoad( drawer, tile, time, image );
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1018,21 +949,6 @@ function compareTiles( previousBest, tile ) {
|
|||||||
return previousBest;
|
return previousBest;
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishLoadingImage( image, callback, successful, jobid ){
|
|
||||||
|
|
||||||
image.onload = null;
|
|
||||||
image.onabort = null;
|
|
||||||
image.onerror = null;
|
|
||||||
|
|
||||||
if ( jobid ) {
|
|
||||||
window.clearTimeout( jobid );
|
|
||||||
}
|
|
||||||
$.requestAnimationFrame( function() {
|
|
||||||
callback( image.src, successful ? image : null);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawTiles( drawer, lastDrawn ){
|
function drawTiles( drawer, lastDrawn ){
|
||||||
var i,
|
var i,
|
||||||
tile,
|
tile,
|
||||||
|
171
src/imageLoader.js
Normal file
171
src/imageLoader.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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 loading a single image for use in a single {@link OpenSeadragon.Tile}.
|
||||||
|
*
|
||||||
|
* @memberof OpenSeadragon
|
||||||
|
* @param {String} source - URL of image to download.
|
||||||
|
* @param {Function} callback - Called once image has finished downloading.
|
||||||
|
*/
|
||||||
|
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 = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates downloading of associated image.
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
start: function(){
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
this.image = new Image();
|
||||||
|
|
||||||
|
if ( _this.crossOriginPolicy !== false ) {
|
||||||
|
this.image.crossOrigin = this.crossOriginPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.image.onload = function(){
|
||||||
|
_this.finish( true );
|
||||||
|
};
|
||||||
|
this.image.onabort = this.image.onerror = function(){
|
||||||
|
_this.finish( false );
|
||||||
|
};
|
||||||
|
|
||||||
|
this.jobId = window.setTimeout( function(){
|
||||||
|
_this.finish( false );
|
||||||
|
}, this.timeout);
|
||||||
|
|
||||||
|
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
|
||||||
|
* @classdesc Handles downloading of a set of images using asynchronous queue pattern.
|
||||||
|
*/
|
||||||
|
$.ImageLoader = function() {
|
||||||
|
|
||||||
|
$.extend( true, this, {
|
||||||
|
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
|
||||||
|
jobQueue: [],
|
||||||
|
jobsInProgress: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ImageLoader.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an unloaded image to the loader queue.
|
||||||
|
* @method
|
||||||
|
* @param {String} src - URL of image to download.
|
||||||
|
* @param {Function} callback - Called once image has been downloaded.
|
||||||
|
*/
|
||||||
|
addJob: function( options ) {
|
||||||
|
var _this = this,
|
||||||
|
complete = function( job ) {
|
||||||
|
completeJob( _this, job, options.callback );
|
||||||
|
},
|
||||||
|
jobOptions = {
|
||||||
|
src: options.src,
|
||||||
|
callback: complete
|
||||||
|
},
|
||||||
|
newJob = new ImageJob( jobOptions );
|
||||||
|
|
||||||
|
if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
|
||||||
|
newJob.start();
|
||||||
|
this.jobsInProgress++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.jobQueue.push( newJob );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback( job.image );
|
||||||
|
}
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user