Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move tile caching code inside tilecache.js. #659

Merged
merged 13 commits into from Jun 12, 2015
3 changes: 2 additions & 1 deletion .editorconfig
@@ -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/**}]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is totally unrelated to the rest of the PR but the content of test/lib and test/data was keeping getting modified due to editorconfig. So I had to do something about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool; good to fix then!

indent_style = space
indent_size = 4
end_of_line = lf
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion src/buttongroup.js
Expand Up @@ -68,7 +68,7 @@ $.ButtonGroup = function( options ) {
*/
this.element = options.element || $.makeNeutralElement( "div" );

// TODO What if there IS an options.group specified?
// TODO What if there IS an options.group specified?
if( !options.group ){
this.label = $.makeNeutralElement( "label" );
//TODO: support labels for ButtonGroups
Expand Down
2 changes: 1 addition & 1 deletion src/iiiftilesource.js
Expand Up @@ -94,7 +94,7 @@ $.IIIFTileSource = function( options ){
// If we're smaller than 256, just use the short side.
options.tileSize = shortDim;
}
this.tile_width = options.tileSize; // So that 'full' gets used for
this.tile_width = options.tileSize; // So that 'full' gets used for
this.tile_height = options.tileSize; // the region below
}

Expand Down
24 changes: 12 additions & 12 deletions src/mousetracker.js
Expand Up @@ -133,7 +133,7 @@
*/
this.element = $.getElement( options.element );
/**
* The number of milliseconds within which a pointer down-up event combination
* The number of milliseconds within which a pointer down-up event combination
* will be treated as a click gesture.
* @member {Number} clickTimeThreshold
* @memberof OpenSeadragon.MouseTracker#
Expand Down Expand Up @@ -244,7 +244,7 @@

// Active pointers lists. Array of GesturePointList objects, one for each pointer device type.
// GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()).
// Active pointers are any pointer being tracked for this element which are in the hit-test area
// Active pointers are any pointer being tracked for this element which are in the hit-test area
// of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
activePointersLists: [],

Expand Down Expand Up @@ -1032,7 +1032,7 @@
$.MouseTracker.mousePointerId = "legacy-mouse";
$.MouseTracker.maxTouchPoints = 10;
}


///////////////////////////////////////////////////////////////////////////////
// Classes and typedefs
Expand Down Expand Up @@ -1078,7 +1078,7 @@
/**
* @class GesturePointList
* @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type.
* Active pointers are any pointer being tracked for this element which are in the hit-test area
* Active pointers are any pointer being tracked for this element which are in the hit-test area
* of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
* @memberof OpenSeadragon.MouseTracker
* @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
Expand Down Expand Up @@ -1198,7 +1198,7 @@
return null;
}
};


///////////////////////////////////////////////////////////////////////////////
// Utility functions
Expand Down Expand Up @@ -1282,7 +1282,7 @@
false
);
}

clearTrackedPointers( tracker );

delegate.tracking = true;
Expand Down Expand Up @@ -1694,7 +1694,7 @@


/**
* Handles 'wheel' events.
* Handles 'wheel' events.
* The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
*
* @private
Expand Down Expand Up @@ -1943,7 +1943,7 @@
handleMouseMove( tracker, event );
}


/**
* This handler is attached to the window object (on the capture phase) to emulate mouse capture.
* onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
Expand Down Expand Up @@ -2191,7 +2191,7 @@
var i,
touchCount = event.changedTouches.length,
gPoints = [];

for ( i = 0; i < touchCount; i++ ) {
gPoints.push( {
id: event.changedTouches[ i ].identifier,
Expand Down Expand Up @@ -2420,7 +2420,7 @@
*/
function startTrackingPointer( pointsList, gPoint ) {

// If isPrimary is not known for the pointer then set it according to our rules:
// If isPrimary is not known for the pointer then set it according to our rules:
// true if the first pointer in the gesture, otherwise false
if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
if ( pointsList.getLength() === 0 ) {
Expand Down Expand Up @@ -2617,7 +2617,7 @@
* Gesture points associated with the event.
* @param {Number} buttonChanged
* The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
* Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
* Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
* only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
*
* @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false.
Expand Down Expand Up @@ -2779,7 +2779,7 @@
* Gesture points associated with the event.
* @param {Number} buttonChanged
* The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
* Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
* Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
* only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
*
* @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false.
Expand Down
37 changes: 16 additions & 21 deletions src/tile.js
Expand Up @@ -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()
Expand All @@ -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%";
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand Down Expand Up @@ -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;
}
Expand Down
46 changes: 40 additions & 6 deletions src/tilecache.js
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is pretty internal, so losing it probably isn't a big deal. Still, probably best to deprecate it (i.e. leave the function but have it $.console.error, and of course write it up in the changelog). You could even leave the code in it, really.

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;
},

Expand Down Expand Up @@ -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
Expand All @@ -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" );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change to the API, so it needs to be documented in the comment above and also written up in the changelog.

$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );

var cutoff = options.cutoff || 0;
Expand All @@ -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++;
Expand All @@ -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-- ) {
Expand All @@ -169,6 +184,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
} else if ( !worstTile ) {
worstTile = prevTile;
worstTileIndex = i;
worstTileRecord = prevTileRecord;
continue;
}

Expand All @@ -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;
}
}
Expand All @@ -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--;
}
Expand All @@ -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;

Expand All @@ -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
});
}
};

Expand Down