Better tile caching for duplicate images

This commit is contained in:
Ian Gilman 2014-11-05 13:48:27 -08:00
parent 8466a91470
commit 8641279890
4 changed files with 137 additions and 32 deletions

View File

@ -33,7 +33,7 @@
*/
(function( $ ){
var TILE_CACHE = {};
/**
* @class Tile
* @memberof OpenSeadragon
@ -241,16 +241,23 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
rendered,
canvas;
if ( !this.loaded || !( this.image || TILE_CACHE[ this.url ] ) ){
if (!this.cacheImageRecord) {
$.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) ){
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
context.globalAlpha = this.opacity;
//context.save();
context.globalAlpha = this.opacity;
//if we are supposed to be rendering fully opaque rectangle,
//ie its done fading or fading is turned off, and if we are drawing
@ -268,24 +275,21 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
}
if( !TILE_CACHE[ this.url ] ){
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 );
TILE_CACHE[ this.url ] = rendered;
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;
}
rendered = TILE_CACHE[ this.url ];
// This gives the application a chance to make image manipulation changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
//rendered.save();
context.drawImage(
rendered.canvas,
0,
@ -297,9 +301,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
size.x,
size.y
);
//rendered.restore();
//context.restore();
},
/**
@ -313,9 +314,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
if ( this.element && this.element.parentNode ) {
this.element.parentNode.removeChild( this.element );
}
if ( TILE_CACHE[ this.url ]){
delete TILE_CACHE[ this.url ];
}
this.element = null;
this.imgElement = null;

View File

@ -43,6 +43,53 @@ var TileRecord = function( options ) {
this.tiledImage = options.tiledImage;
};
// private
var ImageRecord = function(options) {
$.console.assert( options, "[ImageRecord] options is required" );
$.console.assert( options.image, "[ImageRecord] options.image is required" );
this._image = options.image;
this._tiles = [];
};
ImageRecord.prototype = {
destroy: function() {
this._image = null;
this._renderedContext = null;
this._tiles = null;
},
getImage: function() {
return this._image;
},
getRenderedContext: function() {
return this._renderedContext;
},
setRenderedContext: function(renderedContext) {
this._renderedContext = renderedContext;
},
addTile: function(tile) {
this._tiles.push(tile);
},
removeTile: function(tile) {
for (var i = 0; i < this._tiles.length; i++) {
if (this._tiles[i] === tile) {
this._tiles.splice(i, 1);
return;
}
}
$.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile);
},
getTileCount: function() {
return this._tiles.length;
}
};
/**
* @class TileCache
* @memberof OpenSeadragon
@ -54,8 +101,10 @@ var TileRecord = function( options ) {
$.TileCache = function( options ) {
options = options || {};
this._tilesLoaded = [];
this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;
this._tilesLoaded = [];
this._imagesLoaded = [];
this._imagesLoadedCount = 0;
};
$.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
@ -69,7 +118,10 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
/**
* Caches the specified tile, removing an old tile if necessary to stay under the
* maxImageCacheCount specified on construction.
* maxImageCacheCount specified on construction. Note that if multiple tiles reference
* the same image, there may be more tiles than maxImageCacheCount; the goal is to keep
* the number of images below that number. Note, as well, that even the number of images
* 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 {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
@ -80,12 +132,28 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
cacheTile: function( options ) {
$.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.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0;
var insertionIndex = this._tilesLoaded.length;
if ( this._tilesLoaded.length >= this._maxImageCacheCount ) {
var imageRecord = this._imagesLoaded[options.tile.url];
if (!imageRecord) {
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
image: options.tile.image
});
this._imagesLoadedCount++;
}
imageRecord.addTile(options.tile);
options.tile.cacheImageRecord = imageRecord;
// Note that just because we're unloading a tile doesn't necessarily mean
// we're unloading an image. With repeated calls it should sort itself out, though.
if ( this._imagesLoadedCount >= this._maxImageCacheCount ) {
var worstTile = null;
var worstTileIndex = -1;
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
@ -115,7 +183,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
}
if ( worstTile && worstTileIndex >= 0 ) {
worstTile.unload();
this._unloadTile(worstTile);
insertionIndex = worstTileIndex;
}
}
@ -134,11 +202,29 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
tileRecord = this._tilesLoaded[ i ];
if ( tileRecord.tiledImage === tiledImage ) {
tileRecord.tile.unload();
this._unloadTile(tileRecord.tile);
this._tilesLoaded.splice( i, 1 );
i--;
}
}
},
getImageRecord: function(url) {
return this._imagesLoaded[url];
},
// private
_unloadTile: function(tile) {
tile.unload();
tile.cacheImageRecord = null;
var imageRecord = this._imagesLoaded[tile.url];
imageRecord.removeTile(tile);
if (!imageRecord.getTileCount()) {
imageRecord.destroy();
delete this._imagesLoaded[tile.url];
this._imagesLoadedCount--;
}
}
};

View File

@ -485,6 +485,19 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
tiledImage
);
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
});
}
}
if ( tile.loaded ) {
var needsUpdate = blendTile(
tiledImage,

View File

@ -9,12 +9,15 @@
var testInitialOpen = false;
var testOverlays = false;
var testMargins = false;
var testNavigator = false;
var margins;
var config = {
debugMode: true,
zoomPerScroll: 1.02,
showNavigator: true,
showNavigator: testNavigator,
wrapHorizontal: true,
wrapVertical: true,
id: "contentDiv",
prefixUrl: "../../../build/openseadragon/images/"
};
@ -83,7 +86,7 @@
}
// this.crossTest3();
this.basicTest();
this.crossTest2();
},
// ----------
@ -119,7 +122,8 @@
self.viewer.addTiledImage( options );
});
this.viewer.open("../../data/tall.dzi", {
this.viewer.open({
tileSource: "../../data/tall.dzi",
x: 1.5,
y: 0,
width: 1
@ -129,12 +133,13 @@
// ----------
crossTest2: function() {
this.viewer.open([
// {
// tileSource: "../../data/tall.dzi",
// x: 1.5,
// y: 0,
// width: 1
// },
{
tileSource: "../../data/tall.dzi",
x: 1.5,
y: 0,
width: 1
}, {
tileSource: '../../data/wide.dzi',
opacity: 1,
x: 0,
@ -184,7 +189,7 @@
self.viewer.world.addHandler('add-item', function() {
loaded++;
if (loaded === expected) {
self.viewer.viewport.goHome();
self.viewer.viewport.goHome(true);
}
});
@ -208,7 +213,8 @@
}
});
this.viewer.open("../../data/testpattern.dzi", {
this.viewer.open({
tileSource: "../../data/testpattern.dzi",
x: startX,
y: 0,
width: 1
@ -217,7 +223,8 @@
// ----------
bigTest: function() {
this.viewer.open("../../data/testpattern.dzi", {
this.viewer.open({
tileSource: "../../data/testpattern.dzi",
x: -2,
y: -2,
width: 6
@ -246,7 +253,8 @@
}
};
this.viewer.open(dzi, {
this.viewer.open({
tileSource: dzi,
width: 100
});
},