mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-25 14:46:10 +03:00
Merge pull request #450 from openseadragon/ian
WIP: Splitting drawer into drawer/tileCache/tiledImage; adding World
This commit is contained in:
commit
d7991234df
@ -48,7 +48,10 @@ module.exports = function(grunt) {
|
||||
"src/tile.js",
|
||||
"src/overlay.js",
|
||||
"src/drawer.js",
|
||||
"src/viewport.js"
|
||||
"src/viewport.js",
|
||||
"src/tiledimage.js",
|
||||
"src/tilecache.js",
|
||||
"src/world.js"
|
||||
];
|
||||
|
||||
// ----------
|
||||
|
@ -1,6 +1,8 @@
|
||||
OPENSEADRAGON CHANGELOG
|
||||
=======================
|
||||
|
||||
2.0.0: (in progress)
|
||||
|
||||
1.2.0: (in progress)
|
||||
|
||||
* New combined IIIF TileSource for 1.0 through 2.0 (#441)
|
||||
|
1044
src/drawer.js
1044
src/drawer.js
File diff suppressed because it is too large
Load Diff
@ -2243,7 +2243,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
|
||||
debug: nullfunction,
|
||||
info: nullfunction,
|
||||
warn: nullfunction,
|
||||
error: nullfunction
|
||||
error: nullfunction,
|
||||
assert: nullfunction
|
||||
};
|
||||
|
||||
|
||||
|
@ -60,6 +60,13 @@ $.Point = function( x, y ) {
|
||||
};
|
||||
|
||||
$.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
|
||||
/**
|
||||
* @function
|
||||
* @returns {OpenSeadragon.Point} a duplicate of this Point
|
||||
*/
|
||||
clone: function() {
|
||||
return new $.Point(this.x, this.y);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add another Point to this point and return a new Point.
|
||||
|
@ -75,6 +75,13 @@ $.Rect = function( x, y, width, height ) {
|
||||
};
|
||||
|
||||
$.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
|
||||
/**
|
||||
* @function
|
||||
* @returns {OpenSeadragon.Rect} a duplicate of this Rect
|
||||
*/
|
||||
clone: function() {
|
||||
return new $.Rect(this.x, this.y, this.width, this.height);
|
||||
},
|
||||
|
||||
/**
|
||||
* The aspect ratio is simply the ratio of width to height.
|
||||
|
133
src/tilecache.js
Normal file
133
src/tilecache.js
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* OpenSeadragon - TileCache
|
||||
*
|
||||
* 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( $ ){
|
||||
|
||||
var TileRecord = function( params ) {
|
||||
$.console.assert( params, "[TileCache.cacheTile] params is required" );
|
||||
$.console.assert( params.tile, "[TileCache.cacheTile] params.tile is required" );
|
||||
$.console.assert( params.tiledImage, "[TileCache.cacheTile] params.tiledImage is required" );
|
||||
this.tile = params.tile;
|
||||
this.tiledImage = params.tiledImage;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class TileCache
|
||||
* @classdesc
|
||||
*/
|
||||
$.TileCache = function( options ) {
|
||||
options = options || {};
|
||||
|
||||
this._tilesLoaded = [];
|
||||
this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;
|
||||
};
|
||||
|
||||
$.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
|
||||
/**
|
||||
* Returns the total number of tiles that have been loaded by this TileCache.
|
||||
* @method
|
||||
* @returns {Number} - The total number of tiles that have been loaded by
|
||||
* this TileCache.
|
||||
*/
|
||||
numTilesLoaded: function() {
|
||||
return this._tilesLoaded.length;
|
||||
},
|
||||
|
||||
cacheTile: function( params ) {
|
||||
$.console.assert( params, "[TileCache.cacheTile] params is required" );
|
||||
$.console.assert( params.tile, "[TileCache.cacheTile] params.tile is required" );
|
||||
$.console.assert( params.tiledImage, "[TileCache.cacheTile] params.tiledImage is required" );
|
||||
|
||||
var cutoff = params.cutoff || 0;
|
||||
var insertionIndex = this._tilesLoaded.length;
|
||||
|
||||
if ( this._tilesLoaded.length >= this._maxImageCacheCount ) {
|
||||
var worstTile = null;
|
||||
var worstTileIndex = -1;
|
||||
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
|
||||
|
||||
for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
|
||||
prevTileRecord = this._tilesLoaded[ i ];
|
||||
prevTile = prevTileRecord.tile;
|
||||
|
||||
if ( prevTile.level <= cutoff || prevTile.beingDrawn ) {
|
||||
continue;
|
||||
} else if ( !worstTile ) {
|
||||
worstTile = prevTile;
|
||||
worstTileIndex = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
prevTime = prevTile.lastTouchTime;
|
||||
worstTime = worstTile.lastTouchTime;
|
||||
prevLevel = prevTile.level;
|
||||
worstLevel = worstTile.level;
|
||||
|
||||
if ( prevTime < worstTime ||
|
||||
( prevTime == worstTime && prevLevel > worstLevel ) ) {
|
||||
worstTile = prevTile;
|
||||
worstTileIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if ( worstTile && worstTileIndex >= 0 ) {
|
||||
worstTile.unload();
|
||||
insertionIndex = worstTileIndex;
|
||||
}
|
||||
}
|
||||
|
||||
this._tilesLoaded[ insertionIndex ] = new TileRecord({
|
||||
tile: params.tile,
|
||||
tiledImage: params.tiledImage
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all tiles associated with the specified tiledImage.
|
||||
* @method
|
||||
*/
|
||||
clearTilesFor: function( tiledImage ) {
|
||||
var tileRecord;
|
||||
for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
|
||||
tileRecord = this._tilesLoaded[ i ];
|
||||
if ( tileRecord.tiledImage === tiledImage ) {
|
||||
tileRecord.tile.unload();
|
||||
this._tilesLoaded.splice( i, 1 );
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}( OpenSeadragon ));
|
809
src/tiledimage.js
Normal file
809
src/tiledimage.js
Normal file
@ -0,0 +1,809 @@
|
||||
/*
|
||||
* OpenSeadragon - TiledImage
|
||||
*
|
||||
* 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( $ ){
|
||||
|
||||
/**
|
||||
* @class TiledImage
|
||||
* @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
|
||||
* A new instance is created for each TileSource opened.
|
||||
*
|
||||
* @memberof OpenSeadragon
|
||||
*/
|
||||
$.TiledImage = function( options ) {
|
||||
$.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" );
|
||||
$.console.assert( options.drawer, "[TiledImage] options.drawer is required" );
|
||||
$.console.assert( options.viewer, "[TiledImage] options.viewer is required" );
|
||||
$.console.assert( options.imageLoader, "[TiledImage] options.imageLoader is required" );
|
||||
|
||||
this._tileCache = options.tileCache;
|
||||
delete options.tileCache;
|
||||
|
||||
this._drawer = options.drawer;
|
||||
delete options.drawer;
|
||||
|
||||
this._imageLoader = options.imageLoader;
|
||||
delete options.imageLoader;
|
||||
|
||||
this._worldX = options.x || 0;
|
||||
delete options.x;
|
||||
this._worldY = options.y || 0;
|
||||
delete options.y;
|
||||
|
||||
// Ratio of zoomable image height to width.
|
||||
this.normHeight = options.source.dimensions.y / options.source.dimensions.x;
|
||||
|
||||
if ( options.width ) {
|
||||
this._scale = options.width;
|
||||
delete options.width;
|
||||
|
||||
if ( options.height ) {
|
||||
$.console.error( "specifying both width and height to a tiledImage is not supported" );
|
||||
delete options.height;
|
||||
}
|
||||
} else if ( options.height ) {
|
||||
this._scale = options.height / this.normHeight;
|
||||
delete options.height;
|
||||
} else {
|
||||
this._scale = 1;
|
||||
}
|
||||
|
||||
this._worldWidth = this._scale;
|
||||
this._worldHeight = this.normHeight * this._scale;
|
||||
|
||||
$.extend( true, this, {
|
||||
|
||||
//internal state properties
|
||||
viewer: null,
|
||||
tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
|
||||
coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
|
||||
lastDrawn: [], // An unordered list of Tiles drawn last frame.
|
||||
lastResetTime: 0, // Last time for which the tiledImage was reset.
|
||||
midUpdate: false, // Is the tiledImage currently updating the viewport?
|
||||
updateAgain: true, // Does the tiledImage need to update the viewport again?
|
||||
|
||||
//configurable settings
|
||||
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
|
||||
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
|
||||
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
|
||||
immediateRender: $.DEFAULT_SETTINGS.immediateRender,
|
||||
blendTime: $.DEFAULT_SETTINGS.blendTime,
|
||||
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
|
||||
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
|
||||
debugMode: $.DEFAULT_SETTINGS.debugMode,
|
||||
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy
|
||||
|
||||
}, options );
|
||||
};
|
||||
|
||||
$.TiledImage.prototype = /** @lends OpenSeadragon.TiledImage.prototype */{
|
||||
/**
|
||||
* Returns whether the TiledImage is scheduled for an update at the
|
||||
* soonest possible opportunity.
|
||||
* @method
|
||||
* @returns {Boolean} - Whether the TiledImage is scheduled for an update at the
|
||||
* soonest possible opportunity.
|
||||
*/
|
||||
needsUpdate: function() {
|
||||
return this.updateAgain;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all tiles and triggers an update on the next call to
|
||||
* TiledImage.prototype.update().
|
||||
* @method
|
||||
* @return {OpenSeadragon.TiledImage} Chainable.
|
||||
*/
|
||||
reset: function() {
|
||||
this._tileCache.clearTilesFor(this);
|
||||
this.lastResetTime = $.now();
|
||||
this.updateAgain = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Forces the TiledImage to update.
|
||||
* @method
|
||||
* @return {OpenSeadragon.TiledImage} Chainable.
|
||||
*/
|
||||
update: function() {
|
||||
this.midUpdate = true;
|
||||
updateViewport( this );
|
||||
this.midUpdate = false;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the TiledImage (unload current loaded tiles)
|
||||
* @method
|
||||
* @return null
|
||||
*/
|
||||
destroy: function() {
|
||||
this.reset();
|
||||
},
|
||||
|
||||
getWorldBounds: function() {
|
||||
return new $.Rect( this._worldX, this._worldY, this._worldWidth, this._worldHeight );
|
||||
},
|
||||
|
||||
getContentSize: function() {
|
||||
return new $.Point(this.source.dimensions.x, this.source.dimensions.y);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
* Pretty much every other line in this needs to be documented so it's clear
|
||||
* how each piece of this routine contributes to the drawing process. That's
|
||||
* why there are so many TODO's inside this function.
|
||||
*/
|
||||
function updateViewport( tiledImage ) {
|
||||
|
||||
tiledImage.updateAgain = false;
|
||||
|
||||
var tile,
|
||||
level,
|
||||
best = null,
|
||||
haveDrawn = false,
|
||||
currentTime = $.now(),
|
||||
viewportSize = tiledImage.viewport.getContainerSize(),
|
||||
viewportBounds = tiledImage.viewport.getBounds( true ),
|
||||
viewportTL = viewportBounds.getTopLeft(),
|
||||
viewportBR = viewportBounds.getBottomRight(),
|
||||
zeroRatioC = tiledImage.viewport.deltaPixelsFromPoints(
|
||||
tiledImage.source.getPixelRatio( 0 ),
|
||||
true
|
||||
).x * tiledImage._scale,
|
||||
lowestLevel = Math.max(
|
||||
tiledImage.source.minLevel,
|
||||
Math.floor(
|
||||
Math.log( tiledImage.minZoomImageRatio ) /
|
||||
Math.log( 2 )
|
||||
)
|
||||
),
|
||||
highestLevel = Math.min(
|
||||
Math.abs(tiledImage.source.maxLevel),
|
||||
Math.abs(Math.floor(
|
||||
Math.log( zeroRatioC / tiledImage.minPixelRatio ) /
|
||||
Math.log( 2 )
|
||||
))
|
||||
),
|
||||
degrees = tiledImage.viewport.degrees,
|
||||
renderPixelRatioC,
|
||||
renderPixelRatioT,
|
||||
zeroRatioT,
|
||||
optimalRatio,
|
||||
levelOpacity,
|
||||
levelVisibility;
|
||||
|
||||
viewportTL.x -= tiledImage._worldX;
|
||||
viewportTL.y -= tiledImage._worldY;
|
||||
viewportBR.x -= tiledImage._worldX;
|
||||
viewportBR.y -= tiledImage._worldY;
|
||||
|
||||
// Reset tile's internal drawn state
|
||||
while ( tiledImage.lastDrawn.length > 0 ) {
|
||||
tile = tiledImage.lastDrawn.pop();
|
||||
tile.beingDrawn = false;
|
||||
}
|
||||
|
||||
//Change bounds for rotation
|
||||
if (degrees === 90 || degrees === 270) {
|
||||
var rotatedBounds = viewportBounds.rotate( degrees );
|
||||
viewportTL = rotatedBounds.getTopLeft();
|
||||
viewportBR = rotatedBounds.getBottomRight();
|
||||
}
|
||||
|
||||
//Don't draw if completely outside of the viewport
|
||||
if ( !tiledImage.wrapHorizontal &&
|
||||
( viewportBR.x < 0 || viewportTL.x > tiledImage._worldWidth ) ) {
|
||||
return;
|
||||
} else if
|
||||
( !tiledImage.wrapVertical &&
|
||||
( viewportBR.y < 0 || viewportTL.y > tiledImage._worldHeight ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate viewport rect / bounds
|
||||
if ( !tiledImage.wrapHorizontal ) {
|
||||
viewportTL.x = Math.max( viewportTL.x, 0 );
|
||||
viewportBR.x = Math.min( viewportBR.x, tiledImage._worldWidth );
|
||||
}
|
||||
if ( !tiledImage.wrapVertical ) {
|
||||
viewportTL.y = Math.max( viewportTL.y, 0 );
|
||||
viewportBR.y = Math.min( viewportBR.y, tiledImage._worldHeight );
|
||||
}
|
||||
|
||||
// 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 );
|
||||
|
||||
// Update any level that will be drawn
|
||||
var drawLevel; // FIXME: drawLevel should have a more explanatory name
|
||||
for ( level = highestLevel; level >= lowestLevel; level-- ) {
|
||||
drawLevel = false;
|
||||
|
||||
//Avoid calculations for draw if we have already drawn this
|
||||
renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPoints(
|
||||
tiledImage.source.getPixelRatio( level ),
|
||||
true
|
||||
).x * tiledImage._scale;
|
||||
|
||||
if ( ( !haveDrawn && renderPixelRatioC >= tiledImage.minPixelRatio ) ||
|
||||
( level == lowestLevel ) ) {
|
||||
drawLevel = true;
|
||||
haveDrawn = true;
|
||||
} else if ( !haveDrawn ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Perform calculations for draw if we haven't drawn this
|
||||
renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPoints(
|
||||
tiledImage.source.getPixelRatio( level ),
|
||||
false
|
||||
).x * tiledImage._scale;
|
||||
|
||||
zeroRatioT = tiledImage.viewport.deltaPixelsFromPoints(
|
||||
tiledImage.source.getPixelRatio(
|
||||
Math.max(
|
||||
tiledImage.source.getClosestLevel( tiledImage.viewport.containerSize ) - 1,
|
||||
0
|
||||
)
|
||||
),
|
||||
false
|
||||
).x * tiledImage._scale;
|
||||
|
||||
optimalRatio = tiledImage.immediateRender ?
|
||||
1 :
|
||||
zeroRatioT;
|
||||
|
||||
levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 );
|
||||
|
||||
levelVisibility = optimalRatio / Math.abs(
|
||||
optimalRatio - renderPixelRatioT
|
||||
);
|
||||
|
||||
// Update the level and keep track of 'best' tile to load
|
||||
best = updateLevel(
|
||||
tiledImage,
|
||||
haveDrawn,
|
||||
drawLevel,
|
||||
level,
|
||||
levelOpacity,
|
||||
levelVisibility,
|
||||
viewportTL,
|
||||
viewportBR,
|
||||
currentTime,
|
||||
best
|
||||
);
|
||||
|
||||
// Stop the loop if lower-res tiles would all be covered by
|
||||
// already drawn tiles
|
||||
if ( providesCoverage( tiledImage.coverage, level ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the actual drawing
|
||||
drawTiles( tiledImage, tiledImage.lastDrawn );
|
||||
|
||||
// Load the new 'best' tile
|
||||
if ( best ) {
|
||||
loadTile( tiledImage, best, currentTime );
|
||||
// because we haven't finished drawing, so
|
||||
tiledImage.updateAgain = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){
|
||||
|
||||
var x, y,
|
||||
tileTL,
|
||||
tileBR,
|
||||
numberOfTiles,
|
||||
viewportCenter = tiledImage.viewport.pixelFromPoint( tiledImage.viewport.getCenter() );
|
||||
|
||||
|
||||
if( tiledImage.viewer ){
|
||||
/**
|
||||
* <em>- Needs documentation -</em>
|
||||
*
|
||||
* @event update-level
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {Object} havedrawn
|
||||
* @property {Object} level
|
||||
* @property {Object} opacity
|
||||
* @property {Object} visibility
|
||||
* @property {Object} topleft
|
||||
* @property {Object} bottomright
|
||||
* @property {Object} currenttime
|
||||
* @property {Object} best
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
tiledImage.viewer.raiseEvent( 'update-level', {
|
||||
havedrawn: haveDrawn,
|
||||
level: level,
|
||||
opacity: levelOpacity,
|
||||
visibility: levelVisibility,
|
||||
topleft: viewportTL,
|
||||
bottomright: viewportBR,
|
||||
currenttime: currentTime,
|
||||
best: best
|
||||
});
|
||||
}
|
||||
|
||||
//OK, a new drawing so do your calculations
|
||||
tileTL = tiledImage.source.getTileAtPoint( level, viewportTL.divide( tiledImage._scale ));
|
||||
tileBR = tiledImage.source.getTileAtPoint( level, viewportBR.divide( tiledImage._scale ));
|
||||
numberOfTiles = tiledImage.source.getNumTiles( level );
|
||||
|
||||
resetCoverage( tiledImage.coverage, level );
|
||||
|
||||
if ( !tiledImage.wrapHorizontal ) {
|
||||
tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 );
|
||||
}
|
||||
if ( !tiledImage.wrapVertical ) {
|
||||
tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 );
|
||||
}
|
||||
|
||||
for ( x = tileTL.x; x <= tileBR.x; x++ ) {
|
||||
for ( y = tileTL.y; y <= tileBR.y; y++ ) {
|
||||
|
||||
best = updateTile(
|
||||
tiledImage,
|
||||
drawLevel,
|
||||
haveDrawn,
|
||||
x, y,
|
||||
level,
|
||||
levelOpacity,
|
||||
levelVisibility,
|
||||
viewportCenter,
|
||||
numberOfTiles,
|
||||
currentTime,
|
||||
best
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
|
||||
|
||||
var tile = getTile(
|
||||
x, y,
|
||||
level,
|
||||
tiledImage.source,
|
||||
tiledImage.tilesMatrix,
|
||||
currentTime,
|
||||
numberOfTiles,
|
||||
tiledImage._worldWidth,
|
||||
tiledImage._worldHeight
|
||||
),
|
||||
drawTile = drawLevel;
|
||||
|
||||
if( tiledImage.viewer ){
|
||||
/**
|
||||
* <em>- Needs documentation -</em>
|
||||
*
|
||||
* @event update-tile
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Tile} tile
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
tiledImage.viewer.raiseEvent( 'update-tile', {
|
||||
tile: tile
|
||||
});
|
||||
}
|
||||
|
||||
setCoverage( tiledImage.coverage, level, x, y, false );
|
||||
|
||||
if ( !tile.exists ) {
|
||||
return best;
|
||||
}
|
||||
|
||||
if ( haveDrawn && !drawTile ) {
|
||||
if ( isCovered( tiledImage.coverage, level, x, y ) ) {
|
||||
setCoverage( tiledImage.coverage, level, x, y, true );
|
||||
} else {
|
||||
drawTile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !drawTile ) {
|
||||
return best;
|
||||
}
|
||||
|
||||
positionTile(
|
||||
tile,
|
||||
tiledImage.source.tileOverlap,
|
||||
tiledImage.viewport,
|
||||
viewportCenter,
|
||||
levelVisibility,
|
||||
tiledImage
|
||||
);
|
||||
|
||||
if ( tile.loaded ) {
|
||||
var needsUpdate = blendTile(
|
||||
tiledImage,
|
||||
tile,
|
||||
x, y,
|
||||
level,
|
||||
levelOpacity,
|
||||
currentTime
|
||||
);
|
||||
|
||||
if ( needsUpdate ) {
|
||||
tiledImage.updateAgain = true;
|
||||
}
|
||||
} else if ( tile.loading ) {
|
||||
// the tile is already in the download queue
|
||||
// thanks josh1093 for finally translating this typo
|
||||
} else {
|
||||
best = compareTiles( best, tile );
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWidth, worldHeight ) {
|
||||
var xMod,
|
||||
yMod,
|
||||
bounds,
|
||||
exists,
|
||||
url,
|
||||
tile;
|
||||
|
||||
if ( !tilesMatrix[ level ] ) {
|
||||
tilesMatrix[ level ] = {};
|
||||
}
|
||||
if ( !tilesMatrix[ level ][ x ] ) {
|
||||
tilesMatrix[ level ][ x ] = {};
|
||||
}
|
||||
|
||||
if ( !tilesMatrix[ level ][ x ][ y ] ) {
|
||||
xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
|
||||
yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
|
||||
bounds = tileSource.getTileBounds( level, xMod, yMod );
|
||||
exists = tileSource.tileExists( level, xMod, yMod );
|
||||
url = tileSource.getTileUrl( level, xMod, yMod );
|
||||
|
||||
bounds.x += worldWidth * ( x - xMod ) / numTiles.x;
|
||||
bounds.y += worldHeight * ( y - yMod ) / numTiles.y;
|
||||
|
||||
tilesMatrix[ level ][ x ][ y ] = new $.Tile(
|
||||
level,
|
||||
x,
|
||||
y,
|
||||
bounds,
|
||||
exists,
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
tile = tilesMatrix[ level ][ x ][ y ];
|
||||
tile.lastTouchTime = time;
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
function loadTile( tiledImage, tile, time ) {
|
||||
tile.loading = true;
|
||||
tiledImage._imageLoader.addJob({
|
||||
src: tile.url,
|
||||
crossOriginPolicy: tiledImage.crossOriginPolicy,
|
||||
callback: function( image ){
|
||||
onTileLoad( tiledImage, tile, time, image );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onTileLoad( tiledImage, tile, time, image ) {
|
||||
tile.loading = false;
|
||||
|
||||
if ( tiledImage.midUpdate ) {
|
||||
$.console.warn( "Tile load callback in middle of drawing routine." );
|
||||
return;
|
||||
} else if ( !image ) {
|
||||
$.console.log( "Tile %s failed to load: %s", tile, tile.url );
|
||||
if( !tiledImage.debugMode ){
|
||||
tile.exists = false;
|
||||
return;
|
||||
}
|
||||
} else if ( time < tiledImage.lastResetTime ) {
|
||||
$.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url );
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
tiledImage.updateAgain = true;
|
||||
}
|
||||
|
||||
|
||||
function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
|
||||
var boundsTL = tile.bounds.getTopLeft();
|
||||
|
||||
boundsTL.x *= tiledImage._scale;
|
||||
boundsTL.y *= tiledImage._scale;
|
||||
boundsTL.x += tiledImage._worldX;
|
||||
boundsTL.y += tiledImage._worldY;
|
||||
|
||||
var boundsSize = tile.bounds.getSize();
|
||||
|
||||
boundsSize.x *= tiledImage._scale;
|
||||
boundsSize.y *= tiledImage._scale;
|
||||
|
||||
var positionC = viewport.pixelFromPoint( boundsTL, true ),
|
||||
positionT = viewport.pixelFromPoint( boundsTL, false ),
|
||||
sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ),
|
||||
sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ),
|
||||
tileCenter = positionT.plus( sizeT.divide( 2 ) ),
|
||||
tileDistance = viewportCenter.distanceTo( tileCenter );
|
||||
|
||||
if ( !overlap ) {
|
||||
sizeC = sizeC.plus( new $.Point( 1, 1 ) );
|
||||
}
|
||||
|
||||
tile.position = positionC;
|
||||
tile.size = sizeC;
|
||||
tile.distance = tileDistance;
|
||||
tile.visibility = levelVisibility;
|
||||
}
|
||||
|
||||
|
||||
function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
|
||||
var blendTimeMillis = 1000 * tiledImage.blendTime,
|
||||
deltaTime,
|
||||
opacity;
|
||||
|
||||
if ( !tile.blendStart ) {
|
||||
tile.blendStart = currentTime;
|
||||
}
|
||||
|
||||
deltaTime = currentTime - tile.blendStart;
|
||||
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
|
||||
|
||||
if ( tiledImage.alwaysBlend ) {
|
||||
opacity *= levelOpacity;
|
||||
}
|
||||
|
||||
tile.opacity = opacity;
|
||||
|
||||
tiledImage.lastDrawn.push( tile );
|
||||
|
||||
if ( opacity == 1 ) {
|
||||
setCoverage( tiledImage.coverage, level, x, y, true );
|
||||
} else if ( deltaTime < blendTimeMillis ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
* Returns true if the given tile provides coverage to lower-level tiles of
|
||||
* lower resolution representing the same content. If neither x nor y is
|
||||
* given, returns true if the entire visible level provides coverage.
|
||||
*
|
||||
* Note that out-of-bounds tiles provide coverage in this sense, since
|
||||
* there's no content that they would need to cover. Tiles at non-existent
|
||||
* levels that are within the image bounds, however, do not.
|
||||
*/
|
||||
function providesCoverage( coverage, level, x, y ) {
|
||||
var rows,
|
||||
cols,
|
||||
i, j;
|
||||
|
||||
if ( !coverage[ level ] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( x === undefined || y === undefined ) {
|
||||
rows = coverage[ level ];
|
||||
for ( i in rows ) {
|
||||
if ( rows.hasOwnProperty( i ) ) {
|
||||
cols = rows[ i ];
|
||||
for ( j in cols ) {
|
||||
if ( cols.hasOwnProperty( j ) && !cols[ j ] ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
coverage[ level ][ x] === undefined ||
|
||||
coverage[ level ][ x ][ y ] === undefined ||
|
||||
coverage[ level ][ x ][ y ] === true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
* Returns true if the given tile is completely covered by higher-level
|
||||
* tiles of higher resolution representing the same content. If neither x
|
||||
* nor y is given, returns true if the entire visible level is covered.
|
||||
*/
|
||||
function isCovered( coverage, level, x, y ) {
|
||||
if ( x === undefined || y === undefined ) {
|
||||
return providesCoverage( coverage, level + 1 );
|
||||
} else {
|
||||
return (
|
||||
providesCoverage( coverage, level + 1, 2 * x, 2 * y ) &&
|
||||
providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) &&
|
||||
providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) &&
|
||||
providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
* Sets whether the given tile provides coverage or not.
|
||||
*/
|
||||
function setCoverage( coverage, level, x, y, covers ) {
|
||||
if ( !coverage[ level ] ) {
|
||||
$.console.warn(
|
||||
"Setting coverage for a tile before its level's coverage has been reset: %s",
|
||||
level
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !coverage[ level ][ x ] ) {
|
||||
coverage[ level ][ x ] = {};
|
||||
}
|
||||
|
||||
coverage[ level ][ x ][ y ] = covers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
* Resets coverage information for the given level. This should be called
|
||||
* after every draw routine. Note that at the beginning of the next draw
|
||||
* routine, coverage for every visible tile should be explicitly set.
|
||||
*/
|
||||
function resetCoverage( coverage, level ) {
|
||||
coverage[ level ] = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inner
|
||||
* Determines whether the 'last best' tile for the area is better than the
|
||||
* tile in question.
|
||||
*/
|
||||
function compareTiles( previousBest, tile ) {
|
||||
if ( !previousBest ) {
|
||||
return tile;
|
||||
}
|
||||
|
||||
if ( tile.visibility > previousBest.visibility ) {
|
||||
return tile;
|
||||
} else if ( tile.visibility == previousBest.visibility ) {
|
||||
if ( tile.distance < previousBest.distance ) {
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
|
||||
return previousBest;
|
||||
}
|
||||
|
||||
function drawTiles( tiledImage, lastDrawn ){
|
||||
var i,
|
||||
tile,
|
||||
tileKey,
|
||||
viewer,
|
||||
viewport,
|
||||
position,
|
||||
tileSource,
|
||||
collectionTileSource;
|
||||
|
||||
// We need a callback to give image manipulation a chance to happen
|
||||
var drawingHandler = function(args) {
|
||||
if (tiledImage.viewer) {
|
||||
/**
|
||||
* This event is fired just before the tile is drawn giving the application a chance to alter the image.
|
||||
*
|
||||
* NOTE: This event is only fired when the tiledImage is using a <canvas>.
|
||||
*
|
||||
* @event tile-drawing
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Tile} tile
|
||||
* @property {?Object} userData - 'context', 'tile' and 'rendered'.
|
||||
*/
|
||||
tiledImage.viewer.raiseEvent('tile-drawing', args);
|
||||
}
|
||||
};
|
||||
|
||||
for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
|
||||
tile = lastDrawn[ i ];
|
||||
tiledImage._drawer.drawTile( tile );
|
||||
tile.beingDrawn = true;
|
||||
|
||||
if( tiledImage.debugMode ){
|
||||
try{
|
||||
tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i );
|
||||
}catch(e){
|
||||
$.console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if( tiledImage.viewer ){
|
||||
/**
|
||||
* <em>- Needs documentation -</em>
|
||||
*
|
||||
* @event tile-drawn
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Tile} tile
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
tiledImage.viewer.raiseEvent( 'tile-drawn', {
|
||||
tile: tile
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}( OpenSeadragon ));
|
317
src/viewer.js
317
src/viewer.js
@ -151,9 +151,7 @@ $.Viewer = function( options ) {
|
||||
* @memberof OpenSeadragon.Viewer#
|
||||
*/
|
||||
drawer: null,
|
||||
drawers: [],
|
||||
// Container inside the canvas where drawers (layers) are drawn.
|
||||
drawersContainer: null,
|
||||
world: null,
|
||||
/**
|
||||
* Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
|
||||
* @member {OpenSeadragon.Viewport} viewport
|
||||
@ -264,7 +262,6 @@ $.Viewer = function( options ) {
|
||||
this.element = this.element || document.getElementById( this.id );
|
||||
this.canvas = $.makeNeutralElement( "div" );
|
||||
this.keyboardCommandArea = $.makeNeutralElement( "textarea" );
|
||||
this.drawersContainer = $.makeNeutralElement( "div" );
|
||||
this.overlaysContainer = $.makeNeutralElement( "div" );
|
||||
|
||||
this.canvas.className = "openseadragon-canvas";
|
||||
@ -304,7 +301,6 @@ $.Viewer = function( options ) {
|
||||
this.container.insertBefore( this.canvas, this.container.firstChild );
|
||||
this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild );
|
||||
this.element.appendChild( this.container );
|
||||
this.canvas.appendChild( this.drawersContainer );
|
||||
this.canvas.appendChild( this.overlaysContainer );
|
||||
|
||||
//Used for toggling between fullscreen and default container size
|
||||
@ -540,7 +536,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
||||
}
|
||||
|
||||
this.clearOverlays();
|
||||
this.drawersContainer.innerHTML = "";
|
||||
this.overlaysContainer.innerHTML = "";
|
||||
|
||||
if ( this.drawer ) {
|
||||
@ -549,7 +544,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
||||
|
||||
this.source = null;
|
||||
this.drawer = null;
|
||||
this.drawers = [];
|
||||
this.world = null;
|
||||
|
||||
this.viewport = this.preserveViewport ? this.viewport : null;
|
||||
|
||||
@ -627,7 +622,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
||||
this.element = null;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @return {Boolean}
|
||||
@ -1038,77 +1032,71 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a layer.
|
||||
* Add a tiled image to the viewer.
|
||||
* options.tileSource can be anything that {@link OpenSeadragon.Viewer#open}
|
||||
* supports except arrays of images as layers cannot be sequences.
|
||||
* supports except arrays of images.
|
||||
* Note that you can specify options.width or options.height, but not both.
|
||||
* The other dimension will be calculated according to the layer's aspect ratio.
|
||||
* The other dimension will be calculated according to the item's aspect ratio.
|
||||
* @function
|
||||
* @param {Object} options
|
||||
* @param {String|Object|Function} options.tileSource The TileSource of the layer.
|
||||
* @param {Number} [options.opacity=1] The opacity of the layer.
|
||||
* @param {Number} [options.level] The level of the layer. Added on top of
|
||||
* all other layers if not specified.
|
||||
* @param {String|Object|Function} options.tileSource - The TileSource of the item.
|
||||
* @param {Number} [options.index] The index of the item. Added on top of
|
||||
* all other items if not specified.
|
||||
* @param {Number} [options.x=0] The X position for the image in world coordinates.
|
||||
* @param {Number} [options.y=0] The Y position for the image in world coordinates.
|
||||
* @param {Number} [options.width=1] The width for the image in world coordinates.
|
||||
* @param {Number} [options.height] The height for the image in world coordinates.
|
||||
* @returns {OpenSeadragon.Viewer} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:add-layer
|
||||
* @fires OpenSeadragon.Viewer.event:add-layer-failed
|
||||
* @fires OpenSeadragon.World.event:add-item
|
||||
* @fires OpenSeadragon.Viewer.event:add-item-failed
|
||||
*/
|
||||
addLayer: function( options ) {
|
||||
addTiledImage: function( options ) {
|
||||
$.console.assert(options, "[Viewer.addTiledImage] options is required");
|
||||
$.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required");
|
||||
|
||||
var _this = this,
|
||||
tileSource = options.tileSource;
|
||||
|
||||
if ( !this.isOpen() ) {
|
||||
throw new Error( "An image must be loaded before adding layers." );
|
||||
}
|
||||
if ( !tileSource ) {
|
||||
throw new Error( "No tile source provided as new layer." );
|
||||
}
|
||||
if ( this.collectionMode ) {
|
||||
throw new Error( "Layers not supported in collection mode." );
|
||||
throw new Error( "An image must be loaded before adding additional images." );
|
||||
}
|
||||
|
||||
function raiseAddLayerFailed( event ) {
|
||||
function raiseAddItemFailed( event ) {
|
||||
/**
|
||||
* Raised when an error occurs while adding a layer.
|
||||
* @event add-layer-failed
|
||||
* Raised when an error occurs while adding a item.
|
||||
* @event add-item-failed
|
||||
* @memberOf OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {String} message
|
||||
* @property {String} source
|
||||
* @property {Object} options The options passed to the addLayer method.
|
||||
* @property {Object} options The options passed to the addTiledImage method.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
_this.raiseEvent( 'add-layer-failed', event );
|
||||
_this.raiseEvent( 'add-item-failed', event );
|
||||
}
|
||||
|
||||
getTileSourceImplementation( this, tileSource, function( tileSource ) {
|
||||
|
||||
if ( tileSource instanceof Array ) {
|
||||
raiseAddLayerFailed({
|
||||
message: "Sequences can not be added as layers.",
|
||||
raiseAddItemFailed({
|
||||
message: "[Viewer.addTiledImage] Sequences can not be added.",
|
||||
source: tileSource,
|
||||
options: options
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var drawer = new $.Drawer({
|
||||
var tiledImage = new $.TiledImage({
|
||||
viewer: _this,
|
||||
source: tileSource,
|
||||
viewport: _this.viewport,
|
||||
element: _this.drawersContainer,
|
||||
drawer: _this.drawer,
|
||||
tileCache: _this.tileCache,
|
||||
imageLoader: _this.imageLoader,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
opacity: options.opacity !== undefined ?
|
||||
options.opacity : _this.opacity,
|
||||
maxImageCacheCount: _this.maxImageCacheCount,
|
||||
imageLoaderLimit: _this.imageLoaderLimit,
|
||||
minZoomImageRatio: _this.minZoomImageRatio,
|
||||
wrapHorizontal: _this.wrapHorizontal,
|
||||
@ -1117,168 +1105,94 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
||||
blendTime: _this.blendTime,
|
||||
alwaysBlend: _this.alwaysBlend,
|
||||
minPixelRatio: _this.minPixelRatio,
|
||||
timeout: _this.timeout,
|
||||
debugMode: _this.debugMode,
|
||||
debugGridColor: _this.debugGridColor
|
||||
});
|
||||
_this.drawers.push( drawer );
|
||||
if ( options.level !== undefined ) {
|
||||
_this.setLayerLevel( drawer, options.level );
|
||||
}
|
||||
THIS[ _this.hash ].forceRedraw = true;
|
||||
/**
|
||||
* Raised when a layer is successfully added.
|
||||
* @event add-layer
|
||||
* @memberOf OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {Object} options The options passed to the addLayer method.
|
||||
* @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
_this.raiseEvent( 'add-layer', {
|
||||
options: options,
|
||||
drawer: drawer
|
||||
|
||||
_this.world.addItem( tiledImage, {
|
||||
index: options.index
|
||||
});
|
||||
}, function( event ) {
|
||||
event.options = options;
|
||||
raiseAddLayerFailed(event);
|
||||
raiseAddItemFailed(event);
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
addLayer: function( options ) {
|
||||
var self = this;
|
||||
|
||||
$.console.error( "[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead." );
|
||||
|
||||
var addItemHandler = function(event) {
|
||||
self.world.removeHandler("add-item", addItemHandler);
|
||||
self.raiseEvent("add-layer", {
|
||||
options: options,
|
||||
drawer: event.item
|
||||
});
|
||||
};
|
||||
|
||||
var failureHandler = function(event) {
|
||||
self.removeHandler("add-item-failed", failureHandler);
|
||||
self.raiseEvent("add-layer-failed", event);
|
||||
};
|
||||
|
||||
this.world.addHandler("add-item", addItemHandler);
|
||||
this.addHandler("add-item-failed", failureHandler);
|
||||
this.addTiledImage(options);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the layer at the specified level.
|
||||
* @param {Number} level The layer to retrieve level.
|
||||
* @returns {OpenSeadragon.Drawer} The layer at the specified level.
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
getLayerAtLevel: function( level ) {
|
||||
if ( level >= this.drawers.length ) {
|
||||
throw new Error( "Level bigger than number of layers." );
|
||||
}
|
||||
return this.drawers[ level ];
|
||||
$.console.error( "[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead." );
|
||||
return this.world.getItemAt(level);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the level of the layer associated with the given drawer or -1 if not
|
||||
* present.
|
||||
* @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer.
|
||||
* @returns {Number} The level of the layer or -1 if not present.
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
getLevelOfLayer: function( drawer ) {
|
||||
return $.indexOf( this.drawers, drawer );
|
||||
$.console.error( "[Viewer.getLevelOfLayer] this function is deprecated; use World.getIndexOfItem() instead." );
|
||||
return this.world.getIndexOfItem(drawer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of layers used.
|
||||
* @returns {Number} The number of layers used.
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
getLayersCount: function() {
|
||||
return this.drawers.length;
|
||||
$.console.error( "[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead." );
|
||||
return this.world.getItemCount();
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the level of a layer so that it appears over or under others.
|
||||
* @param {OpenSeadragon.Drawer} drawer The underlying drawer of the changing
|
||||
* level layer.
|
||||
* @param {Number} level The new level
|
||||
* @returns {OpenSeadragon.Viewer} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:layer-level-changed
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
setLayerLevel: function( drawer, level ) {
|
||||
var oldLevel = this.getLevelOfLayer( drawer );
|
||||
|
||||
if ( level >= this.drawers.length ) {
|
||||
throw new Error( "Level bigger than number of layers." );
|
||||
}
|
||||
if ( level === oldLevel || oldLevel === -1 ) {
|
||||
return this;
|
||||
}
|
||||
if ( level === 0 || oldLevel === 0 ) {
|
||||
if ( THIS[ this.hash ].sequenced ) {
|
||||
throw new Error( "Cannot reassign base level when in sequence mode." );
|
||||
}
|
||||
// We need to re-assign the base drawer and the source
|
||||
this.drawer = level === 0 ? drawer : this.getLayerAtLevel( level );
|
||||
this.source = this.drawer.source;
|
||||
}
|
||||
this.drawers.splice( oldLevel, 1 );
|
||||
this.drawers.splice( level, 0, drawer );
|
||||
this.drawersContainer.removeChild( drawer.canvas );
|
||||
if ( level === 0 ) {
|
||||
var nextLevelCanvas = this.drawers[ 1 ].canvas;
|
||||
nextLevelCanvas.parentNode.insertBefore( drawer.canvas,
|
||||
nextLevelCanvas );
|
||||
} else {
|
||||
// Insert right after layer at level - 1
|
||||
var prevLevelCanvas = this.drawers[level - 1].canvas;
|
||||
prevLevelCanvas.parentNode.insertBefore( drawer.canvas,
|
||||
prevLevelCanvas.nextSibling );
|
||||
}
|
||||
|
||||
/**
|
||||
* Raised when the order of the layers has been changed.
|
||||
* @event layer-level-changed
|
||||
* @memberOf OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Drawer} drawer - The drawer which level has
|
||||
* been changed
|
||||
* @property {Number} previousLevel - The previous level of the drawer
|
||||
* @property {Number} newLevel - The new level of the drawer
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.raiseEvent( 'layer-level-changed', {
|
||||
drawer: drawer,
|
||||
previousLevel: oldLevel,
|
||||
newLevel: level
|
||||
} );
|
||||
|
||||
return this;
|
||||
$.console.error( "[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead." );
|
||||
return this.world.setItemIndex(drawer, level);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a layer. If there is only one layer, close the viewer.
|
||||
* @function
|
||||
* @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer
|
||||
* to remove
|
||||
* @returns {OpenSeadragon.Viewer} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:remove-layer
|
||||
* @private
|
||||
*/
|
||||
removeLayer: function( drawer ) {
|
||||
var index = this.drawers.indexOf( drawer );
|
||||
if ( index === -1 ) {
|
||||
return this;
|
||||
}
|
||||
if ( index === 0 ) {
|
||||
if ( THIS[ this.hash ].sequenced ) {
|
||||
throw new Error( "Cannot remove base layer when in sequence mode." );
|
||||
}
|
||||
if ( this.drawers.length === 1 ) {
|
||||
this.close();
|
||||
return this;
|
||||
}
|
||||
this.drawer = this.drawers[ 1 ];
|
||||
}
|
||||
|
||||
this.drawers.splice( index, 1 );
|
||||
this.drawersContainer.removeChild( drawer.canvas );
|
||||
/**
|
||||
* Raised when a layer is removed.
|
||||
* @event remove-layer
|
||||
* @memberOf OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.raiseEvent( 'remove-layer', { drawer: drawer } );
|
||||
return this;
|
||||
$.console.error( "[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead." );
|
||||
return this.world.removeItem(drawer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Force the viewer to redraw its drawers.
|
||||
* Force the viewer to redraw its contents.
|
||||
* @returns {OpenSeadragon.Viewer} Chainable.
|
||||
*/
|
||||
forceRedraw: function() {
|
||||
@ -1926,7 +1840,6 @@ function openTileSource( viewer, source, options ) {
|
||||
collectionMode: true,
|
||||
collectionTileSource: _this.source,
|
||||
containerSize: THIS[ _this.hash ].prevContainerSize,
|
||||
contentSize: _this.source.dimensions,
|
||||
springStiffness: _this.springStiffness,
|
||||
animationTime: _this.animationTime,
|
||||
showNavigator: false,
|
||||
@ -1944,7 +1857,6 @@ function openTileSource( viewer, source, options ) {
|
||||
}
|
||||
_this.viewport = _this.viewport ? _this.viewport : new $.Viewport({
|
||||
containerSize: THIS[ _this.hash ].prevContainerSize,
|
||||
contentSize: _this.source.dimensions,
|
||||
springStiffness: _this.springStiffness,
|
||||
animationTime: _this.animationTime,
|
||||
minZoomImageRatio: _this.minZoomImageRatio,
|
||||
@ -1961,23 +1873,52 @@ function openTileSource( viewer, source, options ) {
|
||||
});
|
||||
}
|
||||
|
||||
if( _this.preserveViewport ){
|
||||
_this.viewport.resetContentSize( _this.source.dimensions );
|
||||
}
|
||||
// TODO: what to do about this?
|
||||
// if( _this.preserveViewport ){
|
||||
// _this.viewport.resetContentSize( _this.source.dimensions );
|
||||
// }
|
||||
|
||||
_this.source.overlays = _this.source.overlays || [];
|
||||
|
||||
_this.imageLoader = new $.ImageLoader();
|
||||
|
||||
_this.tileCache = new $.TileCache({
|
||||
maxImageCacheCount: _this.maxImageCacheCount
|
||||
});
|
||||
|
||||
_this.world = new $.World({
|
||||
viewer: _this
|
||||
});
|
||||
|
||||
_this.world.addHandler('add-item', function(event) {
|
||||
_this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
|
||||
THIS[ _this.hash ].forceRedraw = true;
|
||||
});
|
||||
|
||||
_this.world.addHandler('remove-item', function(event) {
|
||||
_this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
|
||||
THIS[ _this.hash ].forceRedraw = true;
|
||||
});
|
||||
|
||||
_this.drawer = new $.Drawer({
|
||||
viewer: _this,
|
||||
viewport: _this.viewport,
|
||||
element: _this.canvas,
|
||||
opacity: _this.opacity,
|
||||
debugGridColor: _this.debugGridColor
|
||||
});
|
||||
|
||||
var tiledImage = new $.TiledImage({
|
||||
viewer: _this,
|
||||
source: _this.source,
|
||||
viewport: _this.viewport,
|
||||
element: _this.drawersContainer,
|
||||
drawer: _this.drawer,
|
||||
tileCache: _this.tileCache,
|
||||
imageLoader: _this.imageLoader,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
opacity: _this.opacity,
|
||||
maxImageCacheCount: _this.maxImageCacheCount,
|
||||
imageLoaderLimit: _this.imageLoaderLimit,
|
||||
minZoomImageRatio: _this.minZoomImageRatio,
|
||||
wrapHorizontal: _this.wrapHorizontal,
|
||||
@ -1986,12 +1927,13 @@ function openTileSource( viewer, source, options ) {
|
||||
blendTime: _this.blendTime,
|
||||
alwaysBlend: _this.alwaysBlend,
|
||||
minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio,
|
||||
timeout: _this.timeout,
|
||||
debugMode: _this.debugMode,
|
||||
debugGridColor: _this.debugGridColor,
|
||||
crossOriginPolicy: _this.crossOriginPolicy
|
||||
});
|
||||
_this.drawers = [_this.drawer];
|
||||
|
||||
_this.world.addItem( tiledImage );
|
||||
_this.viewport.goHome( true );
|
||||
|
||||
// Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons
|
||||
if (!_this.drawer.canRotate()) {
|
||||
@ -2699,7 +2641,7 @@ function updateOnce( viewer ) {
|
||||
}
|
||||
|
||||
if ( animated ) {
|
||||
updateDrawers( viewer );
|
||||
updateWorld( viewer );
|
||||
drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer );
|
||||
if( viewer.navigator ){
|
||||
viewer.navigator.update( viewer.viewport );
|
||||
@ -2714,8 +2656,8 @@ function updateOnce( viewer ) {
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
viewer.raiseEvent( "animation" );
|
||||
} else if ( THIS[ viewer.hash ].forceRedraw || drawersNeedUpdate( viewer ) ) {
|
||||
updateDrawers( viewer );
|
||||
} else if ( THIS[ viewer.hash ].forceRedraw || viewer.world.needsUpdate() ) {
|
||||
updateWorld( viewer );
|
||||
drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer );
|
||||
if( viewer.navigator ){
|
||||
viewer.navigator.update( viewer.viewport );
|
||||
@ -2770,19 +2712,20 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter
|
||||
viewport.fitBounds( newBounds, true );
|
||||
}
|
||||
|
||||
function updateDrawers( viewer ) {
|
||||
for (var i = 0; i < viewer.drawers.length; i++ ) {
|
||||
viewer.drawers[i].update();
|
||||
}
|
||||
}
|
||||
function updateWorld( viewer ) {
|
||||
viewer.drawer.clear();
|
||||
viewer.world.update();
|
||||
|
||||
function drawersNeedUpdate( viewer ) {
|
||||
for (var i = 0; i < viewer.drawers.length; i++ ) {
|
||||
if (viewer.drawers[i].needsUpdate()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
/**
|
||||
* <em>- Needs documentation -</em>
|
||||
*
|
||||
* @event update-viewport
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
viewer.raiseEvent( 'update-viewport', {} );
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -104,44 +104,82 @@ $.Viewport = function( options ) {
|
||||
animationTime: this.animationTime
|
||||
});
|
||||
|
||||
if (this.contentSize) {
|
||||
this.resetContentSize( this.contentSize );
|
||||
} else {
|
||||
this.setHomeBounds(new $.Rect(0, 0, 1, 1), 1);
|
||||
}
|
||||
|
||||
this.goHome( true );
|
||||
this.update();
|
||||
};
|
||||
|
||||
$.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
|
||||
/**
|
||||
* Updates the viewport's home bounds and constraints for the given content size.
|
||||
* @function
|
||||
* @param {OpenSeadragon.Point} contentSize - size of the content in content units
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
* @fires OpenSeadragon.Viewer.event:reset-size
|
||||
*/
|
||||
resetContentSize: function( contentSize ){
|
||||
this.contentSize = contentSize;
|
||||
$.console.assert(contentSize, "[Viewport.resetContentSize] contentSize is required");
|
||||
$.console.assert(contentSize instanceof $.Point, "[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point");
|
||||
$.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0");
|
||||
$.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0");
|
||||
|
||||
this.setHomeBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the viewport's home bounds and constraints.
|
||||
* @function
|
||||
* @param {OpenSeadragon.Rect} bounds - the new bounds in world coordinates
|
||||
* @param {Number} contentFactor - how many content units per world unit
|
||||
* @fires OpenSeadragon.Viewer.event:reset-size
|
||||
*/
|
||||
setHomeBounds: function(bounds, contentFactor) {
|
||||
$.console.assert(bounds, "[Viewport.setHomeBounds] bounds is required");
|
||||
$.console.assert(bounds instanceof $.Rect, "[Viewport.setHomeBounds] bounds must be an OpenSeadragon.Rect");
|
||||
$.console.assert(bounds.width > 0, "[Viewport.setHomeBounds] bounds.width must be greater than 0");
|
||||
$.console.assert(bounds.height > 0, "[Viewport.setHomeBounds] bounds.height must be greater than 0");
|
||||
|
||||
this.homeBounds = bounds.clone();
|
||||
this.contentSize = this.homeBounds.getSize().times(contentFactor);
|
||||
this.contentAspectX = this.contentSize.x / this.contentSize.y;
|
||||
this.contentAspectY = this.contentSize.y / this.contentSize.x;
|
||||
this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
|
||||
this.fitHeightBounds = new $.Rect( 0, 0, this.contentAspectY, this.contentAspectY);
|
||||
|
||||
this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
|
||||
// TODO: seems like fitWidthBounds and fitHeightBounds should be thin slices
|
||||
// across the appropriate axis, centered in the image, rather than what we have
|
||||
// here.
|
||||
this.fitWidthBounds = new $.Rect(this.homeBounds.x, this.homeBounds.y,
|
||||
this.homeBounds.width, this.homeBounds.width);
|
||||
|
||||
this.fitHeightBounds = new $.Rect(this.homeBounds.x, this.homeBounds.y,
|
||||
this.homeBounds.height, this.homeBounds.height);
|
||||
|
||||
if( this.viewer ){
|
||||
/**
|
||||
* Raised when the viewer's content size is reset (see {@link OpenSeadragon.Viewport#resetContentSize}).
|
||||
* Raised when the viewer's content size or home bounds are reset
|
||||
* (see {@link OpenSeadragon.Viewport#resetContentSize},
|
||||
* {@link OpenSeadragon.Viewport#setHomeBounds}).
|
||||
*
|
||||
* @event reset-size
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
|
||||
* @property {OpenSeadragon.Point} contentSize
|
||||
* @property {OpenSeadragon.Rect} homeBounds
|
||||
* @property {Number} contentFactor
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.viewer.raiseEvent( 'reset-size', {
|
||||
contentSize: contentSize
|
||||
contentSize: this.contentSize.clone(),
|
||||
contentFactor: contentFactor,
|
||||
homeBounds: this.homeBounds.clone()
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -154,9 +192,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
if( this.defaultZoomLevel ){
|
||||
return this.defaultZoomLevel;
|
||||
} else {
|
||||
return ( aspectFactor >= 1 ) ?
|
||||
var output = ( aspectFactor >= 1 ) ?
|
||||
1 :
|
||||
aspectFactor;
|
||||
|
||||
return output / this.homeBounds.width;
|
||||
}
|
||||
},
|
||||
|
||||
@ -164,16 +204,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
* @function
|
||||
*/
|
||||
getHomeBounds: function() {
|
||||
var center = this.homeBounds.getCenter( ),
|
||||
width = 1.0 / this.getHomeZoom( ),
|
||||
height = width / this.getAspectRatio();
|
||||
|
||||
return new $.Rect(
|
||||
center.x - ( width / 2.0 ),
|
||||
center.y - ( height / 2.0 ),
|
||||
width,
|
||||
height
|
||||
);
|
||||
return this.homeBounds.clone();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -216,9 +247,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
* @function
|
||||
*/
|
||||
getMaxZoom: function() {
|
||||
var zoom = this.maxZoomLevel ?
|
||||
this.maxZoomLevel :
|
||||
( this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x );
|
||||
var zoom = this.maxZoomLevel;
|
||||
if (!zoom) {
|
||||
zoom = this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x;
|
||||
zoom /= this.homeBounds.width;
|
||||
}
|
||||
|
||||
return Math.max( zoom, this.getHomeZoom() );
|
||||
},
|
||||
@ -347,9 +380,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
verticalThreshold = this.visibilityRatio * newBounds.height;
|
||||
|
||||
left = newBounds.x + newBounds.width;
|
||||
right = 1 - newBounds.x;
|
||||
right = this.homeBounds.width - newBounds.x;
|
||||
top = newBounds.y + newBounds.height;
|
||||
bottom = this.contentAspectY - newBounds.y;
|
||||
bottom = this.homeBounds.height - newBounds.y;
|
||||
|
||||
if ( this.wrapHorizontal ) {
|
||||
//do nothing
|
||||
@ -380,11 +413,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
if ( dx || dy || immediately ) {
|
||||
newBounds.x += dx;
|
||||
newBounds.y += dy;
|
||||
if( newBounds.width > 1 ){
|
||||
newBounds.x = 0.5 - newBounds.width/2;
|
||||
if( newBounds.width > this.homeBounds.width ){
|
||||
newBounds.x = this.homeBounds.width / 2 - newBounds.width/2;
|
||||
}
|
||||
if( newBounds.height > this.contentAspectY ){
|
||||
newBounds.y = this.contentAspectY/2 - newBounds.height/2;
|
||||
if( newBounds.height > this.homeBounds.height){
|
||||
newBounds.y = this.homeBounds.height / 2 - newBounds.height/2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,10 +446,6 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
* @fires OpenSeadragon.Viewer.event:constrain
|
||||
*/
|
||||
applyConstraints: function( immediately ) {
|
||||
if (true) {
|
||||
return; // TEMP
|
||||
}
|
||||
|
||||
var actualZoom = this.getZoom(),
|
||||
constrainedZoom = Math.max(
|
||||
Math.min( actualZoom, this.getMaxZoom() ),
|
||||
@ -739,7 +768,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
|
||||
/**
|
||||
* Raised when rotation has been changed.
|
||||
*
|
||||
* @event update-viewport
|
||||
* @event rotate
|
||||
* @memberof OpenSeadragon.Viewer
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||
|
276
src/world.js
Normal file
276
src/world.js
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* OpenSeadragon - World
|
||||
*
|
||||
* 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( $ ){
|
||||
|
||||
/**
|
||||
* Keeps track of all of the tiled images in the scene.
|
||||
*
|
||||
* @class World
|
||||
* @classdesc
|
||||
*
|
||||
* @memberof OpenSeadragon
|
||||
* @extends OpenSeadragon.EventSource
|
||||
* @param {OpenSeadragon.Options} options - World options.
|
||||
**/
|
||||
$.World = function( options ) {
|
||||
$.console.assert( options.viewer, "[World] options.viewer is required" );
|
||||
|
||||
$.EventSource.call( this );
|
||||
|
||||
this.viewer = options.viewer;
|
||||
this._items = [];
|
||||
this._figureSizes();
|
||||
};
|
||||
|
||||
$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{
|
||||
/**
|
||||
* Add the specified item.
|
||||
* @param {OpenSeadragon.TiledImage} item - The item to add.
|
||||
* @param {Number} options.index - index for the item (optional).
|
||||
* @fires OpenSeadragon.World.event:add-item
|
||||
*/
|
||||
addItem: function( item, options ) {
|
||||
$.console.assert(item, "[World.addItem] item is required");
|
||||
$.console.assert(item instanceof $.TiledImage, "[World.addItem] only TiledImages supported at this time");
|
||||
|
||||
options = options || {};
|
||||
if (options.index !== undefined) {
|
||||
var index = Math.max(0, Math.min(this._items.length, options.index));
|
||||
this._items.splice(index, 0, item);
|
||||
} else {
|
||||
this._items.push( item );
|
||||
}
|
||||
|
||||
this._figureSizes();
|
||||
|
||||
/**
|
||||
* Raised when an item is added to the World.
|
||||
* @event add-item
|
||||
* @memberOf OpenSeadragon.World
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event.
|
||||
* @property {OpenSeadragon.Drawer} item - The item that has been added
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.raiseEvent( 'add-item', {
|
||||
item: item
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the item at the specified index.
|
||||
* @param {Number} index - The item's index.
|
||||
* @returns {OpenSeadragon.TiledImage} The item at the specified index.
|
||||
*/
|
||||
getItemAt: function( index ) {
|
||||
$.console.assert(index !== 'undefined', "[World.getItemAt] index is required");
|
||||
return this._items[ index ];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the index of the given item or -1 if not present.
|
||||
* @param {OpenSeadragon.TiledImage} item - The item.
|
||||
* @returns {Number} The index of the item or -1 if not present.
|
||||
*/
|
||||
getIndexOfItem: function( item ) {
|
||||
$.console.assert(item, "[World.getIndexOfItem] item is required");
|
||||
return $.indexOf( this._items, item );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of items used.
|
||||
* @returns {Number} The number of items used.
|
||||
*/
|
||||
getItemCount: function() {
|
||||
return this._items.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the index of a item so that it appears over or under others.
|
||||
* @param {OpenSeadragon.TiledImage} item - The item to move.
|
||||
* @param {Number} index - The new index.
|
||||
* @fires OpenSeadragon.World.event:item-index-changed
|
||||
*/
|
||||
setItemIndex: function( item, index ) {
|
||||
$.console.assert(item, "[World.setItemIndex] item is required");
|
||||
$.console.assert(index !== 'undefined', "[World.setItemIndex] index is required");
|
||||
|
||||
var oldIndex = this.getIndexOfItem( item );
|
||||
|
||||
if ( index >= this._items.length ) {
|
||||
throw new Error( "Index bigger than number of layers." );
|
||||
}
|
||||
|
||||
if ( index === oldIndex || oldIndex === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._items.splice( oldIndex, 1 );
|
||||
this._items.splice( index, 0, item );
|
||||
|
||||
/**
|
||||
* Raised when the order of the indexes has been changed.
|
||||
* @event item-index-changed
|
||||
* @memberOf OpenSeadragon.World
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
|
||||
* @property {OpenSeadragon.TiledImage} item - The item whose index has
|
||||
* been changed
|
||||
* @property {Number} previousIndex - The previous index of the item
|
||||
* @property {Number} newIndex - The new index of the item
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.raiseEvent( 'item-index-changed', {
|
||||
item: item,
|
||||
previousIndex: oldIndex,
|
||||
newIndex: index
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an item.
|
||||
* @function
|
||||
* @param {OpenSeadragon.TiledImage} item - The item to remove.
|
||||
* @fires OpenSeadragon.World.event:remove-item
|
||||
*/
|
||||
removeItem: function( item ) {
|
||||
$.console.assert(item, "[World.removeItem] item is required");
|
||||
|
||||
var index = this._items.indexOf( item );
|
||||
if ( index === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._items.splice( index, 1 );
|
||||
this._figureSizes();
|
||||
|
||||
/**
|
||||
* Raised when a item is removed.
|
||||
* @event remove-item
|
||||
* @memberOf OpenSeadragon.World
|
||||
* @type {object}
|
||||
* @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
|
||||
* @property {OpenSeadragon.TiledImage} item - The item's underlying item.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.raiseEvent( 'remove-item', { item: item } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all tiles and triggers updates for all items.
|
||||
* @function
|
||||
*/
|
||||
resetItems: function() {
|
||||
for ( var i = 0; i < this._items.length; i++ ) {
|
||||
this._items[i].reset();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates (i.e. draws) all items.
|
||||
* @function
|
||||
*/
|
||||
update: function() {
|
||||
for ( var i = 0; i < this._items.length; i++ ) {
|
||||
this._items[i].update();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @returns {Boolean} true if any items need updating.
|
||||
*/
|
||||
needsUpdate: function() {
|
||||
for ( var i = 0; i < this._items.length; i++ ) {
|
||||
if ( this._items[i].needsUpdate() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @returns {OpenSeadragon.Rect} the smallest rectangle that encloses all items, in world coordinates.
|
||||
*/
|
||||
getHomeBounds: function() {
|
||||
return this._homeBounds.clone();
|
||||
},
|
||||
|
||||
/**
|
||||
* To facilitate zoom constraints, we keep track of the pixel density of the
|
||||
* densest item in the World (i.e. the item whose content size to world size
|
||||
* ratio is the highest) and save it as this "content factor".
|
||||
* @function
|
||||
* @returns {Number} the number of content units per world unit.
|
||||
*/
|
||||
getContentFactor: function() {
|
||||
return this._contentFactor;
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
_figureSizes: function() {
|
||||
if ( !this._items.length ) {
|
||||
this._homeBounds = new $.Rect(0, 0, 1, 1);
|
||||
this._contentSize = new $.Point(1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = this._items[0].getWorldBounds();
|
||||
this._contentFactor = this._items[0].getContentSize().x / bounds.width;
|
||||
var left = bounds.x;
|
||||
var top = bounds.y;
|
||||
var right = bounds.x + bounds.width;
|
||||
var bottom = bounds.y + bounds.height;
|
||||
var box;
|
||||
for ( var i = 1; i < this._items.length; i++ ) {
|
||||
box = this._items[i].getWorldBounds();
|
||||
this._contentFactor = Math.max(this._contentFactor, this._items[i].getContentSize().x / bounds.width);
|
||||
left = Math.min( left, box.x );
|
||||
top = Math.min( top, box.y );
|
||||
right = Math.max( right, box.x + box.width );
|
||||
bottom = Math.max( bottom, box.y + box.height );
|
||||
}
|
||||
|
||||
this._homeBounds = new $.Rect( left, top, right - left, bottom - top );
|
||||
this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor,
|
||||
this._homeBounds.height * this._contentFactor);
|
||||
}
|
||||
});
|
||||
|
||||
}( OpenSeadragon ));
|
@ -133,7 +133,7 @@
|
||||
};
|
||||
|
||||
viewer.addHandler('animation-finish', homeHandler);
|
||||
viewport.goHome(true);
|
||||
viewer.viewport.goHome(true);
|
||||
}
|
||||
|
||||
viewer.addHandler("open", opener);
|
||||
|
7
test/controls.js
vendored
7
test/controls.js
vendored
@ -5,7 +5,12 @@
|
||||
|
||||
module('Controls', {
|
||||
setup: function () {
|
||||
var example = $('<div id="controlsTests"></div>').appendTo("#qunit-fixture");
|
||||
var example = $('<div id="controlsTests"></div>')
|
||||
.css({
|
||||
width: 1000,
|
||||
height: 1000
|
||||
})
|
||||
.appendTo("#qunit-fixture");
|
||||
|
||||
testLog.reset();
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
/* globals $, App */
|
||||
|
||||
(function() {
|
||||
|
||||
var App = {
|
||||
window.App = {
|
||||
init: function() {
|
||||
var self = this;
|
||||
|
||||
@ -38,6 +40,7 @@
|
||||
var addLayerHandler = function( event ) {
|
||||
if ( event.options === options ) {
|
||||
self.viewer.removeHandler( "add-layer", addLayerHandler );
|
||||
self.viewer.viewport.goHome();
|
||||
}
|
||||
};
|
||||
this.viewer.addHandler( "add-layer", addLayerHandler );
|
||||
|
278
test/layers.js
278
test/layers.js
@ -1,278 +0,0 @@
|
||||
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
|
||||
|
||||
( function() {
|
||||
var viewer;
|
||||
|
||||
module( 'Layers', {
|
||||
setup: function() {
|
||||
$( '<div id="layersexample"></div>' ).appendTo( "#qunit-fixture" );
|
||||
|
||||
testLog.reset();
|
||||
|
||||
viewer = OpenSeadragon( {
|
||||
id: 'layersexample',
|
||||
prefixUrl: '/build/openseadragon/images/',
|
||||
springStiffness: 100 // Faster animation = faster tests
|
||||
});
|
||||
},
|
||||
teardown: function() {
|
||||
if ( viewer && viewer.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
$( "#layersexample" ).remove();
|
||||
}
|
||||
} );
|
||||
|
||||
// ----------
|
||||
asyncTest( 'Layers operations', function() {
|
||||
expect( 23 );
|
||||
viewer.addHandler( "open", function( ) {
|
||||
equal( 1, viewer.getLayersCount( ),
|
||||
"One layer should be present after opening." );
|
||||
var options = {
|
||||
tileSource: {
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [ {
|
||||
url: "data/A.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
} ]
|
||||
}
|
||||
};
|
||||
viewer.addLayer( options );
|
||||
viewer.addHandler( "add-layer", function addFirstLayerHandler( event ) {
|
||||
viewer.removeHandler( "add-layer", addFirstLayerHandler );
|
||||
var layer1 = event.drawer;
|
||||
equal( viewer.getLayersCount( ), 2,
|
||||
"2 layers should be present after adding a layer." );
|
||||
equal( options, event.options,
|
||||
"The options should be transmitted via the event." );
|
||||
equal( viewer.getLevelOfLayer( layer1 ), 1,
|
||||
"The first added layer should have a level of 1" );
|
||||
equal( viewer.getLayerAtLevel( 1 ), layer1,
|
||||
"The layer at level 1 should be the first added layer." );
|
||||
|
||||
viewer.addLayer( options );
|
||||
viewer.addHandler( "add-layer", function addSecondLayerHandler( event ) {
|
||||
viewer.removeHandler( "add-layer", addSecondLayerHandler );
|
||||
var layer2 = event.drawer;
|
||||
equal( viewer.getLayersCount( ), 3,
|
||||
"3 layers should be present after adding a second layer." );
|
||||
equal( viewer.getLevelOfLayer( layer2 ), 2,
|
||||
"If not specified, a layer should be added with the highest level." );
|
||||
equal( viewer.getLayerAtLevel( 2 ), layer2,
|
||||
"The layer at level 2 should be the second added layer." );
|
||||
|
||||
viewer.addHandler( "layer-level-changed",
|
||||
function layerLevelChangedHandler( event ) {
|
||||
viewer.removeHandler( "layer-level-changed",
|
||||
layerLevelChangedHandler );
|
||||
equal( event.drawer, layer2,
|
||||
"The layer which changed level should be layer2" );
|
||||
equal( event.previousLevel, 2, "Previous level should be 2." );
|
||||
equal( event.newLevel, 1, "New level should be 1." );
|
||||
});
|
||||
viewer.setLayerLevel( layer2, 1 );
|
||||
equal( viewer.getLevelOfLayer( layer2 ), 1,
|
||||
"Layer2 level should be 1 after setLayerLevel." );
|
||||
equal( viewer.getLevelOfLayer( layer1 ), 2,
|
||||
"Layer1 level should be 2 after setLayerLevel." );
|
||||
equal( viewer.getLayerAtLevel( 1 ), layer2,
|
||||
"The layer at level 1 should be layer2." );
|
||||
equal( viewer.getLayerAtLevel( 2 ), layer1,
|
||||
"The layer at level 2 should be layer1." );
|
||||
|
||||
options.level = 2;
|
||||
options.tileSource.levels[0].url = "data/CCyan.png";
|
||||
options.opacity = 0.5;
|
||||
viewer.addLayer( options );
|
||||
viewer.addHandler( "add-layer", function addThirdLayerHandler( event ) {
|
||||
viewer.removeHandler( "add-layer", addThirdLayerHandler );
|
||||
var layer3 = event.drawer;
|
||||
equal( viewer.getLayersCount( ), 4,
|
||||
"4 layers should be present after adding a third layer." );
|
||||
equal( viewer.getLevelOfLayer( layer3 ), 2,
|
||||
"Layer 3 should be added with level 2." );
|
||||
equal( viewer.getLevelOfLayer( layer2 ), 1,
|
||||
"Layer 2 should stay at level 1." );
|
||||
|
||||
viewer.addHandler( "remove-layer", function removeLayerHandler( event ) {
|
||||
viewer.removeHandler( "remove-layer", removeLayerHandler );
|
||||
|
||||
equal( layer2, event.drawer, "Removed layer should be layer2." );
|
||||
|
||||
equal( viewer.getLevelOfLayer( layer1 ), 2,
|
||||
"Layer 1 should be at level 2." );
|
||||
equal( viewer.getLevelOfLayer( layer2 ), -1,
|
||||
"Layer 2 should be at level -1." );
|
||||
equal( viewer.getLevelOfLayer( layer3 ), 1,
|
||||
"Layer 3 should be at level 1." );
|
||||
|
||||
});
|
||||
viewer.removeLayer( layer2 );
|
||||
|
||||
options.tileSource.levels[0].width = 500;
|
||||
viewer.addHandler( "add-layer-failed", function addLayerFailed( event ) {
|
||||
viewer.removeHandler( "add-layer-failed", addLayerFailed );
|
||||
|
||||
equal( viewer.getLayersCount(), 3 );
|
||||
|
||||
start();
|
||||
});
|
||||
viewer.addLayer( options );
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
});
|
||||
|
||||
asyncTest( 'Sequences as layers', function() {
|
||||
|
||||
var options = {
|
||||
tileSource: [{
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/A.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
}, {
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/BBlue.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
viewer.addHandler( "open", function openHandler() {
|
||||
viewer.removeHandler( "open", openHandler );
|
||||
|
||||
viewer.addHandler( "add-layer-failed",
|
||||
function addLayerFailedHandler( event ) {
|
||||
viewer.removeHandler( "add-layer-failed", addLayerFailedHandler );
|
||||
equal( event.message, "Sequences can not be added as layers." );
|
||||
equal( event.options, options, "Layer failed event should give the options." );
|
||||
start();
|
||||
} );
|
||||
viewer.addLayer( options );
|
||||
|
||||
});
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
});
|
||||
|
||||
|
||||
asyncTest( 'Reassign base layer', function() {
|
||||
|
||||
var options = {
|
||||
tileSource: {
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/A.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
},
|
||||
level: 0
|
||||
};
|
||||
viewer.addHandler( "open", function openHandler( ) {
|
||||
viewer.removeHandler( "open", openHandler );
|
||||
var testPatternDrawer = viewer.drawer;
|
||||
equal( viewer.drawer, testPatternDrawer, "Viewer.drawer should be set to testPatternDrawer." );
|
||||
viewer.addHandler( "add-layer", function addLayerHandler( event ) {
|
||||
viewer.removeHandler( "add-layer", addLayerHandler );
|
||||
var aDrawer = event.drawer;
|
||||
equal( viewer.drawer, aDrawer, "Viewer.drawer should be set to aDrawer." );
|
||||
viewer.setLayerLevel( aDrawer, 1 );
|
||||
equal( viewer.drawer, testPatternDrawer, "Viewer.drawer should be set back to testPatternDrawer." );
|
||||
|
||||
viewer.removeLayer( viewer.drawer );
|
||||
equal( viewer.drawer, aDrawer, "Viewer.drawer must be reassigned when removing base layer." );
|
||||
|
||||
viewer.removeLayer( viewer.drawer );
|
||||
ok( !viewer.isOpen(), "Viewer should be closed when removing last layer." );
|
||||
|
||||
start();
|
||||
});
|
||||
viewer.addLayer( options );
|
||||
});
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
});
|
||||
|
||||
asyncTest( 'Layers and sequences', function() {
|
||||
expect( 1 );
|
||||
|
||||
// TODO: Remove workaround when issue #321 is fixed.
|
||||
// https://github.com/openseadragon/openseadragon/issues/321
|
||||
// viewer.open( [{
|
||||
// type: 'legacy-image-pyramid',
|
||||
// levels: [ {
|
||||
// url: "data/A.png",
|
||||
// width: 1000,
|
||||
// height: 1000
|
||||
// }]
|
||||
// },
|
||||
// {
|
||||
// type: 'legacy-image-pyramid',
|
||||
// levels: [ {
|
||||
// url: "data/BBlue.png",
|
||||
// width: 1000,
|
||||
// height: 1000
|
||||
// }]}] );
|
||||
|
||||
viewer.close();
|
||||
viewer = OpenSeadragon({
|
||||
id: 'layersexample',
|
||||
prefixUrl: '/build/openseadragon/images/',
|
||||
springStiffness: 100, // Faster animation = faster tests
|
||||
tileSources: [{
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/A.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
},
|
||||
{
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/BBlue.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
}]
|
||||
});
|
||||
// End workaround
|
||||
|
||||
var options = {
|
||||
tileSource: {
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/CCyan.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
viewer.addHandler( "open", function openHandler() {
|
||||
viewer.addHandler( "add-layer", function addLayerHandler( event ) {
|
||||
viewer.removeHandler( "add-layer", addLayerHandler );
|
||||
|
||||
var layer = event.drawer;
|
||||
try {
|
||||
viewer.setLayerLevel( layer, 0 );
|
||||
} catch (e) {
|
||||
ok( true );
|
||||
}
|
||||
start();
|
||||
} );
|
||||
viewer.addLayer( options );
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
157
test/multi-image.js
Normal file
157
test/multi-image.js
Normal file
@ -0,0 +1,157 @@
|
||||
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog, expect */
|
||||
|
||||
( function() {
|
||||
var viewer;
|
||||
|
||||
module( 'Multi-Image', {
|
||||
setup: function() {
|
||||
$( '<div id="itemsexample"></div>' ).appendTo( "#qunit-fixture" );
|
||||
|
||||
testLog.reset();
|
||||
|
||||
viewer = OpenSeadragon( {
|
||||
id: 'itemsexample',
|
||||
prefixUrl: '/build/openseadragon/images/',
|
||||
springStiffness: 100 // Faster animation = faster tests
|
||||
});
|
||||
},
|
||||
teardown: function() {
|
||||
if ( viewer && viewer.close ) {
|
||||
viewer.close();
|
||||
}
|
||||
|
||||
viewer = null;
|
||||
$( "#itemsexample" ).remove();
|
||||
}
|
||||
} );
|
||||
|
||||
// ----------
|
||||
asyncTest( 'Multi-image operations', function() {
|
||||
expect( 21 );
|
||||
viewer.addHandler( "open", function( ) {
|
||||
equal( 1, viewer.world.getItemCount( ),
|
||||
"One item should be present after opening." );
|
||||
var options = {
|
||||
tileSource: {
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [ {
|
||||
url: "data/A.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
} ]
|
||||
}
|
||||
};
|
||||
viewer.addTiledImage( options );
|
||||
viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) {
|
||||
viewer.world.removeHandler( "add-item", addFirstItemHandler );
|
||||
var item1 = event.item;
|
||||
equal( viewer.world.getItemCount( ), 2,
|
||||
"2 items should be present after adding a item." );
|
||||
equal( viewer.world.getIndexOfItem( item1 ), 1,
|
||||
"The first added item should have a index of 1" );
|
||||
equal( viewer.world.getItemAt( 1 ), item1,
|
||||
"The item at index 1 should be the first added item." );
|
||||
|
||||
viewer.addTiledImage( options );
|
||||
viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) {
|
||||
viewer.world.removeHandler( "add-item", addSecondItemHandler );
|
||||
var item2 = event.item;
|
||||
equal( viewer.world.getItemCount( ), 3,
|
||||
"3 items should be present after adding a second item." );
|
||||
equal( viewer.world.getIndexOfItem( item2 ), 2,
|
||||
"If not specified, a item should be added with the highest index." );
|
||||
equal( viewer.world.getItemAt( 2 ), item2,
|
||||
"The item at index 2 should be the second added item." );
|
||||
|
||||
viewer.world.addHandler( "item-index-changed",
|
||||
function itemIndexChangedHandler( event ) {
|
||||
viewer.world.removeHandler( "item-index-changed",
|
||||
itemIndexChangedHandler );
|
||||
equal( event.item, item2,
|
||||
"The item which changed index should be item2" );
|
||||
equal( event.previousIndex, 2, "Previous index should be 2." );
|
||||
equal( event.newIndex, 1, "New index should be 1." );
|
||||
});
|
||||
viewer.world.setItemIndex( item2, 1 );
|
||||
equal( viewer.world.getIndexOfItem( item2 ), 1,
|
||||
"Item2 index should be 1 after setItemIndex." );
|
||||
equal( viewer.world.getIndexOfItem( item1 ), 2,
|
||||
"Item1 index should be 2 after setItemIndex." );
|
||||
equal( viewer.world.getItemAt( 1 ), item2,
|
||||
"The item at index 1 should be item2." );
|
||||
equal( viewer.world.getItemAt( 2 ), item1,
|
||||
"The item at index 2 should be item1." );
|
||||
|
||||
options.index = 2;
|
||||
options.tileSource.levels[0].url = "data/CCyan.png";
|
||||
viewer.addTiledImage( options );
|
||||
viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) {
|
||||
viewer.world.removeHandler( "add-item", addThirdItemHandler );
|
||||
var item3 = event.item;
|
||||
equal( viewer.world.getItemCount( ), 4,
|
||||
"4 items should be present after adding a third item." );
|
||||
equal( viewer.world.getIndexOfItem( item3 ), 2,
|
||||
"Item 3 should be added with index 2." );
|
||||
equal( viewer.world.getIndexOfItem( item2 ), 1,
|
||||
"Item 2 should stay at index 1." );
|
||||
|
||||
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
|
||||
viewer.world.removeHandler( "remove-item", removeItemHandler );
|
||||
|
||||
equal( item2, event.item, "Removed item should be item2." );
|
||||
|
||||
equal( viewer.world.getIndexOfItem( item1 ), 2,
|
||||
"Item 1 should be at index 2." );
|
||||
equal( viewer.world.getIndexOfItem( item2 ), -1,
|
||||
"Item 2 should be at index -1." );
|
||||
equal( viewer.world.getIndexOfItem( item3 ), 1,
|
||||
"Item 3 should be at index 1." );
|
||||
|
||||
start();
|
||||
});
|
||||
|
||||
viewer.world.removeItem( item2 );
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
});
|
||||
|
||||
asyncTest( 'Sequences as items', function() {
|
||||
|
||||
var options = {
|
||||
tileSource: [{
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/A.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
}, {
|
||||
type: 'legacy-image-pyramid',
|
||||
levels: [{
|
||||
url: "data/BBlue.png",
|
||||
width: 1000,
|
||||
height: 1000
|
||||
}]
|
||||
}]
|
||||
};
|
||||
|
||||
viewer.addHandler( "open", function openHandler() {
|
||||
viewer.removeHandler( "open", openHandler );
|
||||
|
||||
viewer.addHandler( "add-item-failed",
|
||||
function addItemFailedHandler( event ) {
|
||||
viewer.removeHandler( "add-item-failed", addItemFailedHandler );
|
||||
equal( event.message, "[Viewer.addTiledImage] Sequences can not be added." );
|
||||
equal( event.options, options, "Item failed event should give the options." );
|
||||
start();
|
||||
} );
|
||||
viewer.addTiledImage( options );
|
||||
|
||||
});
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
});
|
||||
|
||||
})();
|
@ -22,14 +22,16 @@
|
||||
reassignments which could be done by other test. -->
|
||||
<script src="/test/polyfills.js"></script>
|
||||
<script src="/test/basic.js"></script>
|
||||
<script src="/test/navigator.js"></script>
|
||||
<script src="/test/strings.js"></script>
|
||||
<script src="/test/formats.js"></script>
|
||||
<script src="/test/utils.js"></script>
|
||||
<script src="/test/events.js"></script>
|
||||
<script src="/test/units.js"></script>
|
||||
<script src="/test/layers.js"></script>
|
||||
<script src="/test/multi-image.js"></script>
|
||||
<script src="/test/overlays.js"></script>
|
||||
<script src="/test/controls.js"></script>
|
||||
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up)
|
||||
so we put them last. -->
|
||||
<script src="/test/navigator.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -134,6 +134,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
testConsole.assert = function(condition, message) {
|
||||
if (condition) {
|
||||
testConsole.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
OpenSeadragon.console = testConsole;
|
||||
} )();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user