mirror of
https://github.com/openseadragon/openseadragon.git
synced 2025-01-31 23:21:42 +03:00
Merge pull request #659 from avandecreme/master
Move tile caching code inside tilecache.js.
This commit is contained in:
commit
a3183183b2
@ -1,7 +1,8 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# We need to specify each folder specifically to avoid including test/lib and test/data
|
||||
[{Gruntfile.js,src/**,test/*,test/demo/**,test/helpers/**,test/modules/**}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
|
@ -2,6 +2,11 @@ OPENSEADRAGON CHANGELOG
|
||||
=======================
|
||||
|
||||
2.0.1: (in progress)
|
||||
* BREAKING CHANGE: the tile does not hold a reference to its image anymore. Only the tile cache keep a reference to images.
|
||||
* DEPRECATION: let ImageRecord.getRenderedContext create the rendered context instead of using ImageRecord.setRenderedContext.
|
||||
* Added "tile-loaded" event on the viewer allowing to modify a tile before it is marked ready to be drawn. (#659)
|
||||
* Added "tile-unloaded" event on the viewer allowing to free up memory one has allocated on a tile. (#659)
|
||||
* Fix flickering tiles with useCanvas=false when no cache is used. (#661)
|
||||
|
||||
2.0.0:
|
||||
|
||||
|
37
src/tile.js
37
src/tile.js
@ -190,7 +190,14 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
|
||||
* @param {Element} container
|
||||
*/
|
||||
drawHTML: function( container ) {
|
||||
if ( !this.loaded || !this.image ) {
|
||||
if (!this.cacheImageRecord) {
|
||||
$.console.warn(
|
||||
'[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
|
||||
this.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !this.loaded ) {
|
||||
$.console.warn(
|
||||
"Attempting to draw tile %s when it's not yet loaded.",
|
||||
this.toString()
|
||||
@ -203,8 +210,7 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
|
||||
|
||||
if ( !this.element ) {
|
||||
this.element = $.makeNeutralElement( "div" );
|
||||
this.imgElement = $.makeNeutralElement( "img" );
|
||||
this.imgElement.src = this.url;
|
||||
this.imgElement = this.cacheImageRecord.getImage().cloneNode();
|
||||
this.imgElement.style.msInterpolationMode = "nearest-neighbor";
|
||||
this.imgElement.style.width = "100%";
|
||||
this.imgElement.style.height = "100%";
|
||||
@ -239,17 +245,18 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
|
||||
|
||||
var position = this.position,
|
||||
size = this.size,
|
||||
rendered,
|
||||
canvas;
|
||||
rendered;
|
||||
|
||||
if (!this.cacheImageRecord) {
|
||||
$.console.warn('[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', this.toString());
|
||||
$.console.warn(
|
||||
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
|
||||
this.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
rendered = this.cacheImageRecord.getRenderedContext();
|
||||
|
||||
if ( !this.loaded || !( this.image || rendered) ){
|
||||
if ( !this.loaded || !rendered ){
|
||||
$.console.warn(
|
||||
"Attempting to draw tile %s when it's not yet loaded.",
|
||||
this.toString()
|
||||
@ -276,19 +283,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
|
||||
|
||||
}
|
||||
|
||||
if(!rendered){
|
||||
canvas = document.createElement( 'canvas' );
|
||||
canvas.width = this.image.width;
|
||||
canvas.height = this.image.height;
|
||||
rendered = canvas.getContext('2d');
|
||||
rendered.drawImage( this.image, 0, 0 );
|
||||
this.cacheImageRecord.setRenderedContext(rendered);
|
||||
//since we are caching the prerendered image on a canvas
|
||||
//allow the image to not be held in memory
|
||||
this.image = null;
|
||||
}
|
||||
|
||||
// This gives the application a chance to make image manipulation changes as we are rendering the image
|
||||
// This gives the application a chance to make image manipulation
|
||||
// changes as we are rendering the image
|
||||
drawingHandler({context: context, tile: this, rendered: rendered});
|
||||
|
||||
context.drawImage(
|
||||
@ -318,7 +314,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
|
||||
|
||||
this.element = null;
|
||||
this.imgElement = null;
|
||||
this.image = null;
|
||||
this.loaded = false;
|
||||
this.loading = false;
|
||||
}
|
||||
|
@ -63,10 +63,23 @@ ImageRecord.prototype = {
|
||||
},
|
||||
|
||||
getRenderedContext: function() {
|
||||
if (!this._renderedContext) {
|
||||
var canvas = document.createElement( 'canvas' );
|
||||
canvas.width = this._image.width;
|
||||
canvas.height = this._image.height;
|
||||
this._renderedContext = canvas.getContext('2d');
|
||||
this._renderedContext.drawImage( this._image, 0, 0 );
|
||||
//since we are caching the prerendered image on a canvas
|
||||
//allow the image to not be held in memory
|
||||
this._image = null;
|
||||
}
|
||||
return this._renderedContext;
|
||||
},
|
||||
|
||||
setRenderedContext: function(renderedContext) {
|
||||
$.console.error("ImageRecord.setRenderedContext is deprecated. " +
|
||||
"The rendered context should be created by the ImageRecord " +
|
||||
"itself when calling ImageRecord.getRenderedContext.");
|
||||
this._renderedContext = renderedContext;
|
||||
},
|
||||
|
||||
@ -126,6 +139,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
* may temporarily surpass that number, but should eventually come back down to the max specified.
|
||||
* @param {Object} options - Tile info.
|
||||
* @param {OpenSeadragon.Tile} options.tile - The tile to cache.
|
||||
* @param {Image} options.image - The image of the tile to cache.
|
||||
* @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
|
||||
* @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
|
||||
* function will release an old tile. The cutoff option specifies a tile level at or below which
|
||||
@ -135,7 +149,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
$.console.assert( options, "[TileCache.cacheTile] options is required" );
|
||||
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
|
||||
$.console.assert( options.tile.url, "[TileCache.cacheTile] options.tile.url is required" );
|
||||
$.console.assert( options.tile.image, "[TileCache.cacheTile] options.tile.image is required" );
|
||||
$.console.assert( options.image, "[TileCache.cacheTile] options.image is required" );
|
||||
$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
|
||||
|
||||
var cutoff = options.cutoff || 0;
|
||||
@ -144,7 +158,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
var imageRecord = this._imagesLoaded[options.tile.url];
|
||||
if (!imageRecord) {
|
||||
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
|
||||
image: options.tile.image
|
||||
image: options.image
|
||||
});
|
||||
|
||||
this._imagesLoadedCount++;
|
||||
@ -158,6 +172,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
|
||||
var worstTile = null;
|
||||
var worstTileIndex = -1;
|
||||
var worstTileRecord = null;
|
||||
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
|
||||
|
||||
for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
|
||||
@ -169,6 +184,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
} else if ( !worstTile ) {
|
||||
worstTile = prevTile;
|
||||
worstTileIndex = i;
|
||||
worstTileRecord = prevTileRecord;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -181,11 +197,12 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
( prevTime == worstTime && prevLevel > worstLevel ) ) {
|
||||
worstTile = prevTile;
|
||||
worstTileIndex = i;
|
||||
worstTileRecord = prevTileRecord;
|
||||
}
|
||||
}
|
||||
|
||||
if ( worstTile && worstTileIndex >= 0 ) {
|
||||
this._unloadTile(worstTile);
|
||||
this._unloadTile(worstTileRecord);
|
||||
insertionIndex = worstTileIndex;
|
||||
}
|
||||
}
|
||||
@ -206,7 +223,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
|
||||
tileRecord = this._tilesLoaded[ i ];
|
||||
if ( tileRecord.tiledImage === tiledImage ) {
|
||||
this._unloadTile(tileRecord.tile);
|
||||
this._unloadTile(tileRecord);
|
||||
this._tilesLoaded.splice( i, 1 );
|
||||
i--;
|
||||
}
|
||||
@ -220,8 +237,11 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
},
|
||||
|
||||
// private
|
||||
_unloadTile: function(tile) {
|
||||
$.console.assert(tile, '[TileCache._unloadTile] tile is required');
|
||||
_unloadTile: function(tileRecord) {
|
||||
$.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');
|
||||
var tile = tileRecord.tile;
|
||||
var tiledImage = tileRecord.tiledImage;
|
||||
|
||||
tile.unload();
|
||||
tile.cacheImageRecord = null;
|
||||
|
||||
@ -232,6 +252,20 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
delete this._imagesLoaded[tile.url];
|
||||
this._imagesLoadedCount--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a tile has just been unloaded from memory.
|
||||
*
|
||||
* @event tile-unloaded
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.
|
||||
* @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.
|
||||
*/
|
||||
tiledImage.viewer.raiseEvent("tile-unloaded", {
|
||||
tile: tile,
|
||||
tiledImage: tiledImage
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -715,8 +715,6 @@ function updateViewport( tiledImage ) {
|
||||
// Load the new 'best' tile
|
||||
if ( best ) {
|
||||
loadTile( tiledImage, best, currentTime );
|
||||
// because we haven't finished drawing, so
|
||||
tiledImage._needsDraw = true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -862,13 +860,8 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
|
||||
if (!tile.loaded) {
|
||||
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
|
||||
if (imageRecord) {
|
||||
tile.loaded = true;
|
||||
tile.image = imageRecord.getImage();
|
||||
|
||||
tiledImage._tileCache.cacheTile({
|
||||
tile: tile,
|
||||
tiledImage: tiledImage
|
||||
});
|
||||
var image = imageRecord.getImage();
|
||||
setTileLoaded(tiledImage, tile, image);
|
||||
}
|
||||
}
|
||||
|
||||
@ -965,16 +958,9 @@ function onTileLoad( tiledImage, tile, time, image ) {
|
||||
}
|
||||
|
||||
var finish = function() {
|
||||
tile.loading = false;
|
||||
tile.loaded = true;
|
||||
tile.image = image;
|
||||
|
||||
var cutoff = Math.ceil( Math.log( tiledImage.source.getTileSize(tile.level) ) / Math.log( 2 ) );
|
||||
tiledImage._tileCache.cacheTile({
|
||||
tile: tile,
|
||||
cutoff: cutoff,
|
||||
tiledImage: tiledImage
|
||||
});
|
||||
var cutoff = Math.ceil( Math.log(
|
||||
tiledImage.source.getTileSize(tile.level) ) / Math.log( 2 ) );
|
||||
setTileLoaded(tiledImage, tile, image, cutoff);
|
||||
};
|
||||
|
||||
// Check if we're mid-update; this can happen on IE8 because image load events for
|
||||
@ -985,10 +971,55 @@ function onTileLoad( tiledImage, tile, time, image ) {
|
||||
// Wait until after the update, in case caching unloads any tiles
|
||||
window.setTimeout( finish, 1);
|
||||
}
|
||||
|
||||
tiledImage._needsDraw = true;
|
||||
}
|
||||
|
||||
function setTileLoaded(tiledImage, tile, image, cutoff) {
|
||||
var increment = 0;
|
||||
|
||||
function getCompletionCallback() {
|
||||
increment++;
|
||||
return completionCallback;
|
||||
}
|
||||
|
||||
function completionCallback() {
|
||||
increment--;
|
||||
if (increment === 0) {
|
||||
tile.loading = false;
|
||||
tile.loaded = true;
|
||||
tiledImage._tileCache.cacheTile({
|
||||
image: image,
|
||||
tile: tile,
|
||||
cutoff: cutoff,
|
||||
tiledImage: tiledImage
|
||||
});
|
||||
tiledImage._needsDraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a tile has just been loaded in memory. That means that the
|
||||
* image has been downloaded and can be modified before being drawn to the canvas.
|
||||
*
|
||||
* @event tile-loaded
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {Image} image - The image of the tile.
|
||||
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
|
||||
* @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
|
||||
* @property {function} getCompletionCallback - A function giving a callback to call
|
||||
* when the asynchronous processing of the image is done. The image will be
|
||||
* marked as entirely loaded when the callback has been called once for each
|
||||
* call to getCompletionCallback.
|
||||
*/
|
||||
tiledImage.viewer.raiseEvent("tile-loaded", {
|
||||
tile: tile,
|
||||
tiledImage: tiledImage,
|
||||
image: image,
|
||||
getCompletionCallback: getCompletionCallback
|
||||
});
|
||||
// In case the completion callback is never called, we at least force it once.
|
||||
getCompletionCallback()();
|
||||
}
|
||||
|
||||
function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
|
||||
var boundsTL = tile.bounds.getTopLeft();
|
||||
|
@ -968,4 +968,103 @@
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
// tile-loaded event tests
|
||||
asyncTest( 'Viewer: tile-loaded event without callback.', function () {
|
||||
|
||||
function tileLoaded ( event ) {
|
||||
viewer.removeHandler( 'tile-loaded', tileLoaded);
|
||||
var tile = event.tile;
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
setTimeout(function() {
|
||||
notOk( tile.loading, "The tile should not be marked as loading.");
|
||||
ok( tile.loaded, "The tile should be marked as loaded.");
|
||||
start();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
viewer.addHandler( 'tile-loaded', tileLoaded);
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
asyncTest( 'Viewer: tile-loaded event with 1 callback.', function () {
|
||||
|
||||
function tileLoaded ( event ) {
|
||||
viewer.removeHandler( 'tile-loaded', tileLoaded);
|
||||
var tile = event.tile;
|
||||
var callback = event.getCompletionCallback();
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
ok( callback, "The event should have a callback.");
|
||||
setTimeout(function() {
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
callback();
|
||||
notOk( tile.loading, "The tile should not be marked as loading.");
|
||||
ok( tile.loaded, "The tile should be marked as loaded.");
|
||||
start();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
viewer.addHandler( 'tile-loaded', tileLoaded);
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
asyncTest( 'Viewer: tile-loaded event with 2 callbacks.', function () {
|
||||
|
||||
function tileLoaded ( event ) {
|
||||
viewer.removeHandler( 'tile-loaded', tileLoaded);
|
||||
var tile = event.tile;
|
||||
var callback1 = event.getCompletionCallback();
|
||||
var callback2 = event.getCompletionCallback();
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
setTimeout(function() {
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
callback1();
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
setTimeout(function() {
|
||||
ok( tile.loading, "The tile should be marked as loading.");
|
||||
notOk( tile.loaded, "The tile should not be marked as loaded.");
|
||||
callback2();
|
||||
notOk( tile.loading, "The tile should not be marked as loading.");
|
||||
ok( tile.loaded, "The tile should be marked as loaded.");
|
||||
start();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
viewer.addHandler( 'tile-loaded', tileLoaded);
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
asyncTest( 'Viewer: tile-unloaded event.', function() {
|
||||
var tiledImage;
|
||||
var tile;
|
||||
|
||||
function tileLoaded( event ) {
|
||||
viewer.removeHandler( 'tile-loaded', tileLoaded);
|
||||
tiledImage = event.tiledImage;
|
||||
tile = event.tile;
|
||||
setTimeout(function() {
|
||||
tiledImage.reset();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function tileUnloaded( event ) {
|
||||
viewer.removeHandler( 'tile-unloaded', tileUnloaded );
|
||||
equal( tile, event.tile,
|
||||
"The unloaded tile should be the same than the loaded one." );
|
||||
equal( tiledImage, event.tiledImage,
|
||||
"The tiledImage of the unloaded tile should be the same than the one of the loaded one." );
|
||||
start();
|
||||
}
|
||||
|
||||
viewer.addHandler( 'tile-loaded', tileLoaded );
|
||||
viewer.addHandler( 'tile-unloaded', tileUnloaded );
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
} )();
|
||||
|
@ -13,8 +13,15 @@
|
||||
|
||||
// ----------
|
||||
asyncTest('basics', function() {
|
||||
var fakeTiledImage0 = {};
|
||||
var fakeTiledImage1 = {};
|
||||
var fakeViewer = {
|
||||
raiseEvent: function() {}
|
||||
};
|
||||
var fakeTiledImage0 = {
|
||||
viewer: fakeViewer
|
||||
};
|
||||
var fakeTiledImage1 = {
|
||||
viewer: fakeViewer
|
||||
};
|
||||
|
||||
var fakeTile0 = {
|
||||
url: 'foo.jpg',
|
||||
@ -58,7 +65,12 @@
|
||||
|
||||
// ----------
|
||||
asyncTest('maxImageCacheCount', function() {
|
||||
var fakeTiledImage0 = {};
|
||||
var fakeViewer = {
|
||||
raiseEvent: function() {}
|
||||
};
|
||||
var fakeTiledImage0 = {
|
||||
viewer: fakeViewer
|
||||
};
|
||||
|
||||
var fakeTile0 = {
|
||||
url: 'different.jpg',
|
||||
|
Loading…
x
Reference in New Issue
Block a user