Merge pull request #1 from openseadragon/master

test
This commit is contained in:
tmund 2015-09-13 18:54:47 -07:00
commit 55cc86e040
36 changed files with 1593 additions and 457 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
# editorconfig.org
root = true
# We need to specify each folder specifically to avoid including test/lib and test/data
[{Gruntfile.js,src/**,test/*,test/demo/**,test/helpers/**,test/modules/**}]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{package.json,.travis.yml,.jshintrc}]
indent_style = space
indent_size = 2

View File

@ -1,14 +1,14 @@
{
"browser": true,
"curly": true,
"eqeqeq": false,
"loopfunc": false,
"noarg": true,
"trailing": true,
"undef": true,
"unused": false,
"browser": true,
"curly": true,
"eqeqeq": false,
"loopfunc": false,
"noarg": true,
"trailing": true,
"undef": true,
"unused": false,
"globals": {
"OpenSeadragon": true
}
"globals": {
"OpenSeadragon": true
}
}

View File

@ -73,7 +73,7 @@ The report shows up at `coverage/html/index.html` viewable in a browser.
OpenSeadragon is truly a community project; we welcome your involvement!
When contributing, please attempt to match the code style already in the codebase. Note that we use four spaces per indentation stop. For more thoughts on code style, see https://github.com/rwldrn/idiomatic.js/.
When contributing, please attempt to match the code style already in the codebase. Note that we use four spaces per indentation stop. For easier setup you can also install [EditorConfig](http://editorconfig.org/) if your IDE is supported. For more thoughts on code style, see [idiomatic.js](https://github.com/rwldrn/idiomatic.js/).
When fixing bugs and adding features, when appropriate please also:
@ -86,6 +86,6 @@ If you're new to open source in general, check out [GitHub's open source intro g
## License
OpenSeadragon is released under the New BSD license. For details, see the file LICENSE.txt.
OpenSeadragon is released under the New BSD license. For details, see the file LICENSE.txt.
[![Build Status](https://secure.travis-ci.org/openseadragon/openseadragon.png?branch=master)](http://travis-ci.org/openseadragon/openseadragon)

View File

@ -1,7 +1,28 @@
OPENSEADRAGON CHANGELOG
=======================
1.3.0: (in progress)
2.1.0: (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
* DEPRECATION: TileSource.getTileSize is deprecated. Use TileSource.getTileWidth and TileSource.getTileHeight instead.
* 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)
* Fixed flickering tiles with useCanvas=false when no cache is used (#661)
* Added additional coordinates conversion methods to TiledImage (#662)
* 'display: none' no longer gets reset on overlays during draw (#668)
* Added `preserveImageSizeOnResize` option (#666)
* Better error reporting for tile load failures (#679)
* Added collectionColumns as a configuration parameter (#680)
* Added support for non-square tiles (#673)
* TileSource.Options objects can now optionally provide tileWidth/tileHeight instead of tileSize for non-square tile support.
* IIIFTileSources will now respect non-square tiles if available.
* Added XDomainRequest as fallback method for ajax requests if XMLHttpRequest fails (for IE < 10) (#693)
* Now avoiding using eval when JSON.parse is available (#696)
* Rotation now works properly on retina display (#708)
* Added option in addTiledImage to replace tiledImage at index (#706)
* Changed resize behaviour to prevent "snapping" to world bounds when constraints allow more space (#711)
2.0.0:
* True multi-image mode (#450)
* BREAKING CHANGE: Passing an array for the tileSources option is no longer enough to trigger sequence mode; you have to set the sequenceMode option to true as well
@ -37,6 +58,8 @@ OPENSEADRAGON CHANGELOG
* Viewport.open supports positioning config properties
* For multi-image open, drawing isn't started until all tileSources have been opened
* You can specify a clip area for each image (only works on browsers that support the HTML5 canvas) (#594)
* Added placeholderFillStyle so image rectangles can be drawn even before their tiles load (#635)
* Ability to set opacity on individual TiledImages (#644)
* Margins option to push the home region in from the edges of the Viewer (#505)
* Rect and Point toString() functions are now consistent: rounding values to nearest hundredth
* Overlays appear in the DOM immediately on open or addOverlay (#507)
@ -57,6 +80,8 @@ OPENSEADRAGON CHANGELOG
* Fixed: Cross Origin policy not working (#613)
* Optimized tile loading by clearing the queue on a re-draw when imageLoaderLimit is set (#616)
* Now animating zoom spring exponentially (#631)
* Added http://editorconfig.org/ config file (#637)
* Keyboard pan speed is now the same regardless of zoom level (#645)
1.2.1:

View File

@ -1,20 +1,20 @@
{
"name": "OpenSeadragon",
"version": "1.2.1",
"version": "2.0.0",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.5.0",
"grunt-text-replace": "^0.3.11",
"grunt-contrib-compress": "^0.9.1",
"grunt-contrib-concat": "^0.4.0",
"grunt-git-describe": "^2.3.2",
"grunt-contrib-connect": "^0.7.1",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-compress": "^0.9.1",
"grunt-contrib-connect": "^0.7.1",
"qunitjs": "^1.14.0",
"grunt-qunit-istanbul": "^0.4.5"
"grunt-git-describe": "^2.3.2",
"grunt-qunit-istanbul": "^0.5.0",
"grunt-text-replace": "^0.3.11",
"qunitjs": "^1.18.0"
},
"scripts": {
"test": "grunt test"

View File

@ -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

View File

@ -42,11 +42,9 @@
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
* @param {Number} [options.opacity=1] - See opacity in {@link OpenSeadragon.Options} for details.
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/
$.Drawer = function( options ) {
var _this = this;
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
@ -72,7 +70,9 @@ $.Drawer = function( options ) {
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
this.opacity = options.opacity === undefined ? $.DEFAULT_SETTINGS.opacity : options.opacity;
if (options.opacity) {
$.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
}
this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
/**
@ -96,6 +96,13 @@ $.Drawer = function( options ) {
*/
this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
/**
* Sketch canvas used to temporarily draw tiles which cannot be drawn directly
* to the main canvas due to opacity. Lazily initialized.
*/
this.sketchCanvas = null;
this.sketchContext = null;
/**
* @member {Element} element
* @memberof OpenSeadragon.Drawer#
@ -160,8 +167,11 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* @return {OpenSeadragon.Drawer} Chainable.
*/
setOpacity: function( opacity ) {
this.opacity = opacity;
$.setElementOpacity( this.canvas, this.opacity, true );
$.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
var world = this.viewer.world;
for (var i = 0; i < world.getItemCount(); i++) {
world.getItemAt( i ).setOpacity( opacity );
}
return this;
},
@ -170,7 +180,16 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* @returns {Number}
*/
getOpacity: function() {
return this.opacity;
$.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
var world = this.viewer.world;
var maxOpacity = 0;
for (var i = 0; i < world.getItemCount(); i++) {
var opacity = world.getItemAt( i ).getOpacity();
if ( opacity > maxOpacity ) {
maxOpacity = opacity;
}
}
return maxOpacity;
},
// deprecated
@ -214,6 +233,8 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
//force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
this.canvas.width = 1;
this.canvas.height = 1;
this.sketchCanvas = null;
this.sketchContext = null;
},
/**
@ -227,189 +248,260 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
this.canvas.height != viewportSize.y ) {
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
if ( this.sketchCanvas !== null ) {
this.sketchCanvas.width = this.canvas.width;
this.sketchCanvas.height = this.canvas.height;
}
}
this.context.clearRect( 0, 0, viewportSize.x, viewportSize.y );
this._clear();
}
},
_clear: function ( useSketch ) {
if ( !this.useCanvas ) {
return;
}
var context = this._getContext( useSketch );
var canvas = context.canvas;
context.clearRect( 0, 0, canvas.width, canvas.height );
},
/**
* Translates from OpenSeadragon viewer rectangle to drawer rectangle.
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle: function(rectangle) {
var topLeft = this.viewport.pixelFromPoint(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPoints(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
},
/**
* Draws the given tile.
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered})
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
* where <code>rendered</code> is the context with the pre-drawn image.
*/
drawTile: function( tile, drawingHandler ) {
drawTile: function( tile, drawingHandler, useSketch ) {
$.console.assert(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
if ( this.useCanvas ) {
var context = this._getContext( useSketch );
// TODO do this in a more performant way
// specifically, don't save,rotate,restore every time we draw a tile
if( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.viewport.degrees );
tile.drawCanvas( this.context, drawingHandler );
this._restoreRotationChanges( tile );
this._offsetForRotation( tile, this.viewport.degrees, useSketch );
tile.drawCanvas( context, drawingHandler );
this._restoreRotationChanges( tile, useSketch );
} else {
tile.drawCanvas( this.context, drawingHandler );
tile.drawCanvas( context, drawingHandler );
}
} else {
tile.drawHTML( this.canvas );
}
},
_getContext: function( useSketch ) {
var context = this.context;
if ( useSketch ) {
if (this.sketchCanvas === null) {
this.sketchCanvas = document.createElement( "canvas" );
this.sketchCanvas.width = this.canvas.width;
this.sketchCanvas.height = this.canvas.height;
this.sketchContext = this.sketchCanvas.getContext( "2d" );
}
context = this.sketchContext;
}
return context;
},
// private
saveContext: function() {
saveContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
this._getContext( useSketch ).save();
},
// private
restoreContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
this._getContext( useSketch ).restore();
},
// private
setClip: function(rect, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
context.beginPath();
context.rect(rect.x, rect.y, rect.width, rect.height);
context.clip();
},
// private
drawRectangle: function(rect, fillStyle, useSketch) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
context.save();
context.fillStyle = fillStyle;
context.fillRect(rect.x, rect.y, rect.width, rect.height);
context.restore();
},
/**
* Blends the sketch canvas in the main canvas.
* @param {Float} opacity The opacity of the blending.
* @returns {undefined}
*/
blendSketch: function(opacity) {
if (!this.useCanvas || !this.sketchCanvas) {
return;
}
this.context.save();
},
// private
restoreContext: function() {
if (!this.useCanvas) {
return;
}
this.context.globalAlpha = opacity;
this.context.drawImage(this.sketchCanvas, 0, 0);
this.context.restore();
},
// private
setClip: function(rect) {
if (!this.useCanvas) {
drawDebugInfo: function( tile, count, i ){
if ( !this.useCanvas ) {
return;
}
this.context.beginPath();
this.context.rect(rect.x, rect.y, rect.width, rect.height);
this.context.clip();
},
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
context.strokeStyle = this.debugGridColor;
context.fillStyle = this.debugGridColor;
// private
drawDebugInfo: function( tile, count, i ){
if ( this.useCanvas ) {
this.context.save();
this.context.lineWidth = 2 * $.pixelDensityRatio;
this.context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
this.context.strokeStyle = this.debugGridColor;
this.context.fillStyle = this.debugGridColor;
if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.canvas, this.context, this.viewport.degrees );
}
this.context.strokeRect(
tile.position.x * $.pixelDensityRatio,
tile.position.y * $.pixelDensityRatio,
tile.size.x * $.pixelDensityRatio,
tile.size.y * $.pixelDensityRatio
);
var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
// Rotate the text the right way around.
this.context.translate( tileCenterX, tileCenterY );
this.context.rotate( Math.PI / 180 * -this.viewport.degrees );
this.context.translate( -tileCenterX, -tileCenterY );
if( tile.x === 0 && tile.y === 0 ){
this.context.fillText(
"Zoom: " + this.viewport.getZoom(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 30) * $.pixelDensityRatio
);
this.context.fillText(
"Pan: " + this.viewport.getBounds().toString(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 20) * $.pixelDensityRatio
);
}
this.context.fillText(
"Level: " + tile.level,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 20) * $.pixelDensityRatio
);
this.context.fillText(
"Column: " + tile.x,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 30) * $.pixelDensityRatio
);
this.context.fillText(
"Row: " + tile.y,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 40) * $.pixelDensityRatio
);
this.context.fillText(
"Order: " + i + " of " + count,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 50) * $.pixelDensityRatio
);
this.context.fillText(
"Size: " + tile.size.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 60) * $.pixelDensityRatio
);
this.context.fillText(
"Position: " + tile.position.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 70) * $.pixelDensityRatio
);
if ( this.viewport.degrees !== 0 ) {
this._restoreRotationChanges( tile, this.canvas, this.context );
}
this.context.restore();
if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.viewport.degrees );
}
context.strokeRect(
tile.position.x * $.pixelDensityRatio,
tile.position.y * $.pixelDensityRatio,
tile.size.x * $.pixelDensityRatio,
tile.size.y * $.pixelDensityRatio
);
var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
// Rotate the text the right way around.
context.translate( tileCenterX, tileCenterY );
context.rotate( Math.PI / 180 * -this.viewport.degrees );
context.translate( -tileCenterX, -tileCenterY );
if( tile.x === 0 && tile.y === 0 ){
context.fillText(
"Zoom: " + this.viewport.getZoom(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 30) * $.pixelDensityRatio
);
context.fillText(
"Pan: " + this.viewport.getBounds().toString(),
tile.position.x * $.pixelDensityRatio,
(tile.position.y - 20) * $.pixelDensityRatio
);
}
context.fillText(
"Level: " + tile.level,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 20) * $.pixelDensityRatio
);
context.fillText(
"Column: " + tile.x,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 30) * $.pixelDensityRatio
);
context.fillText(
"Row: " + tile.y,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 40) * $.pixelDensityRatio
);
context.fillText(
"Order: " + i + " of " + count,
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 50) * $.pixelDensityRatio
);
context.fillText(
"Size: " + tile.size.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 60) * $.pixelDensityRatio
);
context.fillText(
"Position: " + tile.position.toString(),
(tile.position.x + 10) * $.pixelDensityRatio,
(tile.position.y + 70) * $.pixelDensityRatio
);
if ( this.viewport.degrees !== 0 ) {
this._restoreRotationChanges( tile );
}
context.restore();
},
// private
debugRect: function(rect) {
if ( this.useCanvas ) {
this.context.save();
this.context.lineWidth = 2 * $.pixelDensityRatio;
this.context.strokeStyle = this.debugGridColor;
this.context.fillStyle = this.debugGridColor;
var context = this.context;
context.save();
context.lineWidth = 2 * $.pixelDensityRatio;
context.strokeStyle = this.debugGridColor;
context.fillStyle = this.debugGridColor;
this.context.strokeRect(
context.strokeRect(
rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio
);
this.context.restore();
context.restore();
}
},
// private
_offsetForRotation: function( tile, degrees ){
_offsetForRotation: function( tile, degrees, useSketch ){
var cx = this.canvas.width / 2,
cy = this.canvas.height / 2,
px = tile.position.x - cx,
py = tile.position.y - cy;
cy = this.canvas.height / 2;
this.context.save();
var context = this._getContext( useSketch );
context.save();
this.context.translate(cx, cy);
this.context.rotate( Math.PI / 180 * degrees);
tile.position.x = px;
tile.position.y = py;
context.translate(cx, cy);
context.rotate( Math.PI / 180 * degrees);
context.translate(-cx, -cy);
},
// private
_restoreRotationChanges: function( tile ){
var cx = this.canvas.width / 2,
cy = this.canvas.height / 2,
px = tile.position.x + cx,
py = tile.position.y + cy;
tile.position.x = px;
tile.position.y = py;
this.context.restore();
_restoreRotationChanges: function( tile, useSketch ){
var context = this._getContext( useSketch );
context.restore();
},
// private

View File

@ -55,14 +55,19 @@ $.IIIFTileSource = function( options ){
options.tileSizePerScaleFactor = {};
// N.B. 2.0 renamed scale_factors to scaleFactors
if ( this.tile_width ) {
if ( this.tile_width && this.tile_height ) {
options.tileWidth = this.tile_width;
options.tileHeight = this.tile_height;
} else if ( this.tile_width ) {
options.tileSize = this.tile_width;
} else if ( this.tile_height ) {
options.tileSize = this.tile_height;
} else if ( this.tiles ) {
// Version 2.0 forwards
if ( this.tiles.length == 1 ) {
options.tileSize = this.tiles[0].width;
options.tileWidth = this.tiles[0].width;
// Use height if provided, otherwise assume square tiles and use width.
options.tileHeight = this.tiles[0].height || this.tiles[0].width;
this.scale_factors = this.tiles[0].scaleFactors;
} else {
// Multiple tile sizes at different levels
@ -71,13 +76,15 @@ $.IIIFTileSource = function( options ){
for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) {
var scaleFactor = this.tiles[t].scaleFactors[sf];
this.scale_factors.push(scaleFactor);
options.tileSizePerScaleFactor[scaleFactor] = this.tiles[t].width;
options.tileSizePerScaleFactor[scaleFactor] = {
width: this.tiles[t].width,
height: this.tiles[t].height || this.tiles[t].width
};
}
}
}
} else {
// use the largest of tileOptions that is smaller than the short dimension
var shortDim = Math.min( this.height, this.width ),
tileOptions = [256,512,1024],
smallerTiles = [];
@ -94,8 +101,6 @@ $.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_height = options.tileSize; // the region below
}
if ( !options.maxLevel ) {
@ -117,6 +122,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
* @param {Object|Array} data
* @param {String} optional - url
*/
supports: function( data, url ) {
// Version 2.0 and forwards
if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
@ -181,20 +187,34 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
},
/**
* Return the tileSize for the given level.
* Return the tileWidth for the given level.
* @function
* @param {Number} level
*/
getTileSize: function( level ){
*/
getTileWidth: function( level ) {
var scaleFactor = Math.pow(2, this.maxLevel - level);
// cache it in case any external code is going to read it directly
if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
this.tileSize = this.tileSizePerScaleFactor[scaleFactor];
return this.tileSizePerScaleFactor[scaleFactor].width;
}
return this.tileSize;
return this._tileWidth;
},
/**
* Return the tileHeight for the given level.
* @function
* @param {Number} level
*/
getTileHeight: function( level ) {
var scaleFactor = Math.pow(2, this.maxLevel - level);
if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
return this.tileSizePerScaleFactor[scaleFactor].height;
}
return this._tileHeight;
},
/**
* Responsible for retreiving the url which will return an image for the
* region specified by the given x, y, and level components.
@ -216,7 +236,8 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
levelHeight = Math.ceil( this.height * scale ),
//## iiif region
tileSize,
tileWidth,
tileHeight,
iiifTileSizeWidth,
iiifTileSizeHeight,
iiifRegion,
@ -228,9 +249,10 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifQuality,
uri;
tileSize = this.getTileSize(level);
iiifTileSizeWidth = Math.ceil( tileSize / scale );
iiifTileSizeHeight = iiifTileSizeWidth;
tileWidth = this.getTileWidth(level);
tileHeight = this.getTileHeight(level);
iiifTileSizeWidth = Math.ceil( tileWidth / scale );
iiifTileSizeHeight = Math.ceil( tileHeight / scale );
if ( this['@context'].indexOf('/1.0/context.json') > -1 ||
this['@context'].indexOf('/1.1/context.json') > -1 ||
@ -240,7 +262,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifQuality = "default.jpg";
}
if ( levelWidth < tileSize && levelHeight < tileSize ){
if ( levelWidth < tileWidth && levelHeight < tileHeight ){
iiifSize = levelWidth + ",";
iiifRegion = 'full';
} else {

View File

@ -51,6 +51,7 @@ function ImageJob ( options ) {
}
ImageJob.prototype = {
errorMsg: null,
start: function(){
var _this = this;
@ -64,10 +65,12 @@ ImageJob.prototype = {
_this.finish( true );
};
this.image.onabort = this.image.onerror = function(){
_this.errorMsg = "Image load aborted";
_this.finish( false );
};
this.jobId = window.setTimeout( function(){
_this.errorMsg = "Image load exceeded timeout";
_this.finish( false );
}, this.timeout);
@ -173,7 +176,7 @@ function completeJob( loader, job, callback ) {
loader.jobsInProgress++;
}
callback( job.image );
callback( job.image, job.errorMsg );
}
}( OpenSeadragon ));

View File

@ -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#
@ -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: [],
@ -1032,7 +1032,7 @@
$.MouseTracker.mousePointerId = "legacy-mouse";
$.MouseTracker.maxTouchPoints = 10;
}
///////////////////////////////////////////////////////////////////////////////
// Classes and typedefs
@ -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.
@ -1198,7 +1198,7 @@
return null;
}
};
///////////////////////////////////////////////////////////////////////////////
// Utility functions
@ -1282,7 +1282,7 @@
false
);
}
clearTrackedPointers( tracker );
delegate.tracking = true;
@ -1694,7 +1694,7 @@
/**
* Handles 'wheel' events.
* Handles 'wheel' events.
* The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
*
* @private
@ -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.
@ -2191,7 +2191,7 @@
var i,
touchCount = event.changedTouches.length,
gPoints = [];
for ( i = 0; i < touchCount; i++ ) {
gPoints.push( {
id: event.changedTouches[ i ].identifier,
@ -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 ) {
@ -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.
@ -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.

View File

@ -108,7 +108,9 @@ $.Navigator = function( options ){
immediateRender: true,
blendTime: 0,
animationTime: 0,
autoResize: options.autoResize
autoResize: options.autoResize,
// prevent resizing the navigator from adding unwanted space around the image
minZoomImageRatio: 1.0
});
options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;

View File

@ -204,7 +204,12 @@
* If 0, adjusts to fit viewer.
*
* @property {Number} [opacity=1]
* Opacity of the drawer (1=opaque, 0=transparent)
* Default opacity of the tiled images (1=opaque, 0=transparent)
*
* @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
* Draws a colored rectangle behind the tile if it is not loaded yet.
* You can pass a CSS color value like "#FF8800".
* When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern.
*
* @property {Number} [degrees=0]
* Initial rotation.
@ -236,7 +241,7 @@
* @property {Number} [minZoomImageRatio=0.9]
* The minimum percentage ( expressed as a number between 0 and 1 ) of
* the viewport height or width at which the zoom out will be constrained.
* Setting it to 0, for example will allow you to zoom out infinitly.
* Setting it to 0, for example will allow you to zoom out infinity.
*
* @property {Number} [maxZoomPixelRatio=1.1]
* The maximum ratio to allow a zoom-in to affect the highest level pixel
@ -247,6 +252,9 @@
* @property {Boolean} [autoResize=true]
* Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
*
* @property {Boolean} [preserveImageSizeOnResize=false]
* Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).
*
* @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
*
@ -262,7 +270,7 @@
* Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.
*
* @property {Number} [imageLoaderLimit=0]
* The maximum number of image requests to make concurrently. By default
* The maximum number of image requests to make concurrently. By default
* it is set to 0 allowing the browser to make the maximum number of
* image requests in parallel as allowed by the browsers policy.
*
@ -348,7 +356,7 @@
* @property {Boolean} [showNavigator=false]
* Set to true to make the navigator minimap appear.
*
* @property {Boolean} [navigatorId=navigator-GENERATED DATE]
* @property {String} [navigatorId=navigator-GENERATED DATE]
* The ID of a div to hold the navigator minimap.
* If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored.
* If an ID is not specified, a div element will be generated and placed on top of the main image.
@ -551,6 +559,10 @@
* If collectionMode is true, specifies how many rows the grid should have. Use 1 to make a line.
* If collectionLayout is 'vertical', specifies how many columns instead.
*
* @property {Number} [collectionColumns=0]
* If collectionMode is true, specifies how many columns the grid should have. Use 1 to make a line.
* If collectionLayout is 'vertical', specifies how many rows instead. Ignored if collectionRows is not set to a falsy value.
*
* @property {String} [collectionLayout='horizontal']
* If collectionMode is true, specifies whether to arrange vertically or horizontally.
*
@ -982,6 +994,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
pixelsPerWheelLine: 40,
autoResize: true,
preserveImageSizeOnResize: false, // requires autoResize=true
//DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE
@ -1013,10 +1026,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
navigatorRotate: true,
// INITIAL ROTATION
degrees: 0,
degrees: 0,
// APPEARANCE
opacity: 1,
opacity: 1,
placeholderFillStyle: null,
//REFERENCE STRIP SETTINGS
showReferenceStrip: false,
@ -1029,6 +1043,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
//COLLECTION VISUALIZATION SETTINGS
collectionRows: 3, //or columns depending on layout
collectionColumns: 0, //columns in horizontal layout, rows in vertical layout
collectionLayout: 'horizontal', //vertical
collectionMode: false,
collectionTileSize: 800,
@ -2030,8 +2045,40 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
request.onreadystatechange = function(){};
if ( $.isFunction( onError ) ) {
onError( request, e );
if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest
var xdr = new XDomainRequest();
if (xdr) {
xdr.onload = function (e) {
if ( $.isFunction( onSuccess ) ) {
onSuccess({ // Faking an xhr object
responseText: xdr.responseText,
status: 200, // XDomainRequest doesn't support status codes, so we just fake one! :/
statusText: 'OK'
});
}
};
xdr.onerror = function (e) {
if ( $.isFunction ( onError ) ) {
onError({ // Faking an xhr object
responseText: xdr.responseText,
status: 444, // 444 No Response
statusText: 'An error happened. Due to an XDomainRequest deficiency we can not extract any information about this error. Upgrade your browser.'
});
}
};
try {
xdr.open('GET', url);
xdr.send();
} catch (e2) {
if ( $.isFunction( onError ) ) {
onError( request, e );
}
}
}
} else {
if ( $.isFunction( onError ) ) {
onError( request, e );
}
}
}
},
@ -2161,6 +2208,24 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
return $.parseXml( string );
},
/**
* Parses a JSON string into a Javascript object.
* @function
* @param {String} string
* @returns {Object}
*/
parseJSON: function(string) {
if (window.JSON && window.JSON.parse) {
$.parseJSON = window.JSON.parse;
} else {
// Should only be used by IE8 in non standards mode
$.parseJSON = function(string) {
/*jshint evil:true*/
return eval('(' + string + ')');
};
}
return $.parseJSON(string);
},
/**
* Reports whether the image format is supported for tiling in this

View File

@ -282,7 +282,10 @@
style.left = position.x + "px";
style.top = position.y + "px";
style.position = "absolute";
style.display = 'block';
if (style.display != 'none') {
style.display = 'block';
}
if ( scales ) {
style.width = size.x + "px";

View File

@ -67,7 +67,7 @@ $.Tile = function(level, x, y, bounds, exists, url) {
this.y = y;
/**
* Where this tile fits, in normalized coordinates
* @member {OpenSeadragon.Point} bounds
* @member {OpenSeadragon.Rect} bounds
* @memberof OpenSeadragon.Tile#
*/
this.bounds = bounds;
@ -190,7 +190,14 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* @param {Element} container
*/
drawHTML: function( container ) {
if ( !this.loaded || !this.image ) {
if (!this.cacheImageRecord) {
$.console.warn(
'[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
if ( !this.loaded ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
@ -203,8 +210,7 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
if ( !this.element ) {
this.element = $.makeNeutralElement( "div" );
this.imgElement = $.makeNeutralElement( "img" );
this.imgElement.src = this.url;
this.imgElement = this.cacheImageRecord.getImage().cloneNode();
this.imgElement.style.msInterpolationMode = "nearest-neighbor";
this.imgElement.style.width = "100%";
this.imgElement.style.height = "100%";
@ -239,17 +245,18 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
var position = this.position,
size = this.size,
rendered,
canvas;
rendered;
if (!this.cacheImageRecord) {
$.console.warn('[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', this.toString());
$.console.warn(
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
rendered = this.cacheImageRecord.getRenderedContext();
if ( !this.loaded || !( this.image || rendered) ){
if ( !this.loaded || !rendered ){
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
@ -276,19 +283,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
}
if(!rendered){
canvas = document.createElement( 'canvas' );
canvas.width = this.image.width;
canvas.height = this.image.height;
rendered = canvas.getContext('2d');
rendered.drawImage( this.image, 0, 0 );
this.cacheImageRecord.setRenderedContext(rendered);
//since we are caching the prerendered image on a canvas
//allow the image to not be held in memory
this.image = null;
}
// This gives the application a chance to make image manipulation changes as we are rendering the image
// This gives the application a chance to make image manipulation
// changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
context.drawImage(
@ -318,7 +314,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
this.element = null;
this.imgElement = null;
this.image = null;
this.loaded = false;
this.loading = false;
}

View File

@ -63,10 +63,23 @@ ImageRecord.prototype = {
},
getRenderedContext: function() {
if (!this._renderedContext) {
var canvas = document.createElement( 'canvas' );
canvas.width = this._image.width;
canvas.height = this._image.height;
this._renderedContext = canvas.getContext('2d');
this._renderedContext.drawImage( this._image, 0, 0 );
//since we are caching the prerendered image on a canvas
//allow the image to not be held in memory
this._image = null;
}
return this._renderedContext;
},
setRenderedContext: function(renderedContext) {
$.console.error("ImageRecord.setRenderedContext is deprecated. " +
"The rendered context should be created by the ImageRecord " +
"itself when calling ImageRecord.getRenderedContext.");
this._renderedContext = renderedContext;
},
@ -126,6 +139,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
* may temporarily surpass that number, but should eventually come back down to the max specified.
* @param {Object} options - Tile info.
* @param {OpenSeadragon.Tile} options.tile - The tile to cache.
* @param {Image} options.image - The image of the tile to cache.
* @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
* @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
* function will release an old tile. The cutoff option specifies a tile level at or below which
@ -135,7 +149,6 @@ $.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.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0;
@ -143,8 +156,9 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
var imageRecord = this._imagesLoaded[options.tile.url];
if (!imageRecord) {
$.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
image: options.tile.image
image: options.image
});
this._imagesLoadedCount++;
@ -158,6 +172,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
var worstTile = null;
var worstTileIndex = -1;
var worstTileRecord = null;
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
@ -169,6 +184,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
} else if ( !worstTile ) {
worstTile = prevTile;
worstTileIndex = i;
worstTileRecord = prevTileRecord;
continue;
}
@ -181,11 +197,12 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
( prevTime == worstTime && prevLevel > worstLevel ) ) {
worstTile = prevTile;
worstTileIndex = i;
worstTileRecord = prevTileRecord;
}
}
if ( worstTile && worstTileIndex >= 0 ) {
this._unloadTile(worstTile);
this._unloadTile(worstTileRecord);
insertionIndex = worstTileIndex;
}
}
@ -206,7 +223,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
tileRecord = this._tilesLoaded[ i ];
if ( tileRecord.tiledImage === tiledImage ) {
this._unloadTile(tileRecord.tile);
this._unloadTile(tileRecord);
this._tilesLoaded.splice( i, 1 );
i--;
}
@ -220,8 +237,11 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
},
// private
_unloadTile: function(tile) {
$.console.assert(tile, '[TileCache._unloadTile] tile is required');
_unloadTile: function(tileRecord) {
$.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');
var tile = tileRecord.tile;
var tiledImage = tileRecord.tiledImage;
tile.unload();
tile.cacheImageRecord = null;
@ -232,6 +252,20 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
delete this._imagesLoaded[tile.url];
this._imagesLoadedCount--;
}
/**
* Triggered when a tile has just been unloaded from memory.
*
* @event tile-unloaded
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.
* @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.
*/
tiledImage.viewer.raiseEvent("tile-unloaded", {
tile: tile,
tiledImage: tiledImage
});
}
};

View File

@ -64,7 +64,9 @@
* @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
*/
$.TiledImage = function( options ) {
@ -126,21 +128,23 @@ $.TiledImage = function( options ) {
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.
_midDraw: false, // Is the tiledImage currently updating the viewport?
_needsDraw: true, // Does the tiledImage need to update the viewport again?
_midDraw: false, // Is the tiledImage currently updating the viewport?
_needsDraw: true, // Does the tiledImage need to update the viewport again?
//configurable settings
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime,
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
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime,
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,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity
}, options );
@ -402,6 +406,83 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
);
},
/**
* Convert pixel coordinates relative to the viewer element to image
* coordinates.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
viewerElementToImageCoordinates: function( pixel ) {
var point = this.viewport.pointFromPixel( pixel, true );
return this.viewportToImageCoordinates( point );
},
/**
* Convert pixel coordinates relative to the image to
* viewer element coordinates.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
imageToViewerElementCoordinates: function( pixel ) {
var point = this.imageToViewportCoordinates( pixel );
return this.viewport.pixelFromPoint( point, true );
},
/**
* Convert pixel coordinates relative to the window to image coordinates.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
windowToImageCoordinates: function( pixel ) {
var viewerCoordinates = pixel.minus(
OpenSeadragon.getElementPosition( this.viewer.element ));
return this.viewerElementToImageCoordinates( viewerCoordinates );
},
/**
* Convert image coordinates to pixel coordinates relative to the window.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
imageToWindowCoordinates: function( pixel ) {
var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
return viewerCoordinates.plus(
OpenSeadragon.getElementPosition( this.viewer.element ));
},
/**
* Convert a viewport zoom to an image zoom.
* Image zoom: ratio of the original image size to displayed image size.
* 1 means original image size, 0.5 half size...
* Viewport zoom: ratio of the displayed image's width to viewport's width.
* 1 means identical width, 2 means image's width is twice the viewport's width...
* @function
* @param {Number} viewportZoom The viewport zoom
* @returns {Number} imageZoom The image zoom
*/
viewportToImageZoom: function( viewportZoom ) {
var ratio = this._scaleSpring.current.value *
this.viewport._containerInnerSize.x / this.source.dimensions.x;
return ratio * viewportZoom ;
},
/**
* Convert an image zoom to a viewport zoom.
* Image zoom: ratio of the original image size to displayed image size.
* 1 means original image size, 0.5 half size...
* Viewport zoom: ratio of the displayed image's width to viewport's width.
* 1 means identical width, 2 means image's width is twice the viewport's width...
* Note: not accurate with multi-image.
* @function
* @param {Number} imageZoom The image zoom
* @returns {Number} viewportZoom The viewport zoom
*/
imageToViewportZoom: function( imageZoom ) {
var ratio = this._scaleSpring.current.value *
this.viewport._containerInnerSize.x / this.source.dimensions.x;
return imageZoom / ratio;
},
/**
* Sets the TiledImage's position in the world.
* @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.
@ -484,6 +565,21 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._needsDraw = true;
},
/**
* @returns {Number} The TiledImage's current opacity.
*/
getOpacity: function() {
return this.opacity;
},
/**
* @param {Number} opacity Opacity the tiled image should be drawn at.
*/
setOpacity: function(opacity) {
this.opacity = opacity;
this._needsDraw = true;
},
// private
_setScale: function(scale, immediately) {
var sameTarget = (this._scaleSpring.target.value === scale);
@ -696,8 +792,6 @@ function updateViewport( tiledImage ) {
// Load the new 'best' tile
if ( best ) {
loadTile( tiledImage, best, currentTime );
// because we haven't finished drawing, so
tiledImage._needsDraw = true;
}
}
@ -843,13 +937,8 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
if (!tile.loaded) {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
if (imageRecord) {
tile.loaded = true;
tile.image = imageRecord.getImage();
tiledImage._tileCache.cacheTile({
tile: tile,
tiledImage: tiledImage
});
var image = imageRecord.getImage();
setTileLoaded(tiledImage, tile, image);
}
}
@ -922,8 +1011,8 @@ function loadTile( tiledImage, tile, time ) {
tiledImage._imageLoader.addJob({
src: tile.url,
crossOriginPolicy: tiledImage.crossOriginPolicy,
callback: function( image ){
onTileLoad( tiledImage, tile, time, image );
callback: function( image, errorMsg ){
onTileLoad( tiledImage, tile, time, image, errorMsg );
},
abort: function() {
tile.loading = false;
@ -931,9 +1020,9 @@ function loadTile( tiledImage, tile, time ) {
});
}
function onTileLoad( tiledImage, tile, time, image ) {
function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
if ( !image ) {
$.console.log( "Tile %s failed to load: %s", tile, tile.url );
$.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
if( !tiledImage.debugMode ){
tile.loading = false;
tile.exists = false;
@ -946,16 +1035,9 @@ function onTileLoad( tiledImage, tile, time, image ) {
}
var finish = function() {
tile.loading = false;
tile.loaded = true;
tile.image = image;
var cutoff = Math.ceil( Math.log( tiledImage.source.getTileSize(tile.level) ) / Math.log( 2 ) );
tiledImage._tileCache.cacheTile({
tile: tile,
cutoff: cutoff,
tiledImage: tiledImage
});
var cutoff = Math.ceil( Math.log(
tiledImage.source.getTileWidth(tile.level) ) / Math.log( 2 ) );
setTileLoaded(tiledImage, tile, image, cutoff);
};
// Check if we're mid-update; this can happen on IE8 because image load events for
@ -966,10 +1048,55 @@ function onTileLoad( tiledImage, tile, time, image ) {
// Wait until after the update, in case caching unloads any tiles
window.setTimeout( finish, 1);
}
tiledImage._needsDraw = true;
}
function setTileLoaded(tiledImage, tile, image, cutoff) {
var increment = 0;
function getCompletionCallback() {
increment++;
return completionCallback;
}
function completionCallback() {
increment--;
if (increment === 0) {
tile.loading = false;
tile.loaded = true;
tiledImage._tileCache.cacheTile({
image: image,
tile: tile,
cutoff: cutoff,
tiledImage: tiledImage
});
tiledImage._needsDraw = true;
}
}
/**
* Triggered when a tile has just been loaded in memory. That means that the
* image has been downloaded and can be modified before being drawn to the canvas.
*
* @event tile-loaded
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {Image} image - The image of the tile.
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
* @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
* @property {function} getCompletionCallback - A function giving a callback to call
* when the asynchronous processing of the image is done. The image will be
* marked as entirely loaded when the callback has been called once for each
* call to getCompletionCallback.
*/
tiledImage.viewer.raiseEvent("tile-loaded", {
tile: tile,
tiledImage: tiledImage,
image: image,
getCompletionCallback: getCompletionCallback
});
// In case the completion callback is never called, we at least force it once.
getCompletionCallback()();
}
function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
var boundsTL = tile.bounds.getTopLeft();
@ -1148,42 +1275,49 @@ function compareTiles( previousBest, tile ) {
return previousBest;
}
function drawTiles( tiledImage, lastDrawn ){
function drawTiles( tiledImage, lastDrawn ) {
var i,
tile,
tileKey,
viewer,
viewport,
position,
tileSource;
tile;
if ( tiledImage.opacity <= 0 ) {
drawDebugInfo( tiledImage, lastDrawn );
return;
}
var useSketch = tiledImage.opacity < 1;
if ( useSketch ) {
tiledImage._drawer._clear( true );
}
var usedClip = false;
if (tiledImage._clip) {
tiledImage._drawer.saveContext();
if ( tiledImage._clip ) {
tiledImage._drawer.saveContext(useSketch);
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
var topLeft = tiledImage.viewport.pixelFromPoint(box.getTopLeft(), true);
var size = tiledImage.viewport.deltaPixelsFromPoints(box.getSize(), true);
box = new OpenSeadragon.Rect(topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio);
tiledImage._drawer.setClip(box);
var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
tiledImage._drawer.setClip(clipRect, useSketch);
usedClip = true;
}
if ( tiledImage.placeholderFillStyle && lastDrawn.length === 0 ) {
var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
var fillStyle = null;
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context);
}
else {
fillStyle = tiledImage.placeholderFillStyle;
}
tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);
}
for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
tile = lastDrawn[ i ];
tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler );
tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch );
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>
@ -1203,8 +1337,26 @@ function drawTiles( tiledImage, lastDrawn ){
}
}
if (usedClip) {
tiledImage._drawer.restoreContext();
if ( usedClip ) {
tiledImage._drawer.restoreContext( useSketch );
}
if ( useSketch ) {
tiledImage._drawer.blendSketch( tiledImage.opacity );
}
drawDebugInfo( tiledImage, lastDrawn );
}
function drawDebugInfo( tiledImage, lastDrawn ) {
if( tiledImage.debugMode ) {
for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
var tile = lastDrawn[ i ];
try {
tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i );
} catch(e) {
$.console.error(e);
}
}
}
}

View File

@ -73,6 +73,11 @@
* The size of the tiles to assumed to make up each pyramid layer in pixels.
* Tile size determines the point at which the image pyramid must be
* divided into a matrix of smaller images.
* Use options.tileWidth and options.tileHeight to support non-square tiles.
* @param {Number} [options.tileWidth]
* The width of the tiles to assumed to make up each pyramid layer in pixels.
* @param {Number} [options.tileHeight]
* The height of the tiles to assumed to make up each pyramid layer in pixels.
* @param {Number} [options.tileOverlap]
* The number of pixels each tile is expected to overlap touching tiles.
* @param {Number} [options.minLevel]
@ -137,13 +142,6 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
* @member {OpenSeadragon.Point} dimensions
* @memberof OpenSeadragon.TileSource#
*/
/**
* The size of the image tiles used to compose the image.
* Please note that tileSize may be deprecated in a future release.
* Instead the getTileSize(level) function should be used.
* @member {Number} tileSize
* @memberof OpenSeadragon.TileSource#
*/
/**
* The overlap in pixels each tile shares with its adjacent neighbors.
* @member {Number} tileOverlap
@ -174,7 +172,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//async mechanism set some safe defaults first
this.aspectRatio = 1;
this.dimensions = new $.Point( 10, 10 );
this.tileSize = 0;
this._tileWidth = 0;
this._tileHeight = 0;
this.tileOverlap = 0;
this.minLevel = 0;
this.maxLevel = 0;
@ -191,7 +190,29 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.aspectRatio = ( options.width && options.height ) ?
( options.width / options.height ) : 1;
this.dimensions = new $.Point( options.width, options.height );
this.tileSize = options.tileSize ? options.tileSize : 0;
if ( this.tileSize ){
this._tileWidth = this._tileHeight = this.tileSize;
delete this.tileSize;
} else {
if( this.tileWidth ){
// We were passed tileWidth in options, but we want to rename it
// with a leading underscore to make clear that it is not safe to directly modify it
this._tileWidth = this.tileWidth;
delete this.tileWidth;
} else {
this._tileWidth = 0;
}
if( this.tileHeight ){
// See note above about renaming this.tileWidth
this._tileHeight = this.tileHeight;
delete this.tileHeight;
} else {
this._tileHeight = 0;
}
}
this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
this.minLevel = options.minLevel ? options.minLevel : 0;
this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
@ -212,16 +233,42 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
$.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
getTileSize: function( level ) {
$.console.error(
"[TileSource.getTileSize] is deprecated." +
"Use TileSource.getTileWidth() and TileSource.getTileHeight() instead"
);
return this._tileWidth;
},
/**
* Return the tileSize for a given level.
* Subclasses should override this if tileSizes can be different at different levels
* Return the tileWidth for a given level.
* Subclasses should override this if tileWidth can be different at different levels
* such as in IIIFTileSource. Code should use this function rather than reading
* from .tileSize directly. tileSize may be deprecated in a future release.
* from ._tileWidth directly.
* @function
* @param {Number} level
*/
getTileSize: function( level ) {
return this.tileSize;
getTileWidth: function( level ) {
if (!this._tileWidth) {
return this.getTileSize(level);
}
return this._tileWidth;
},
/**
* Return the tileHeight for a given level.
* Subclasses should override this if tileHeight can be different at different levels
* such as in IIIFTileSource. Code should use this function rather than reading
* from ._tileHeight directly.
* @function
* @param {Number} level
*/
getTileHeight: function( level ) {
if (!this._tileHeight) {
return this.getTileSize(level);
}
return this._tileHeight;
},
/**
@ -250,8 +297,8 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
*/
getNumTiles: function( level ) {
var scale = this.getLevelScale( level ),
x = Math.ceil( scale * this.dimensions.x / this.getTileSize(level) ),
y = Math.ceil( scale * this.dimensions.y / this.getTileSize(level) );
x = Math.ceil( scale * this.dimensions.x / this.getTileWidth(level) ),
y = Math.ceil( scale * this.dimensions.y / this.getTileHeight(level) );
return new $.Point( x, y );
},
@ -277,10 +324,15 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
var i,
tilesPerSide,
tiles;
for( i = this.minLevel; i < this.maxLevel; i++ ){
tiles = this.getNumTiles( i );
tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.getTileSize(i) );
if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){
tilesPerSide = new $.Point(
Math.floor( rect.x / this.getTileWidth(i) ),
Math.floor( rect.y / this.getTileHeight(i) )
);
if( tiles.x + 1 >= tilesPerSide.x || tiles.y + 1 >= tilesPerSide.y ){
break;
}
}
@ -293,9 +345,9 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
* @param {OpenSeadragon.Point} point
*/
getTileAtPoint: function( level, point ) {
var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ),
tx = Math.floor( pixel.x / this.getTileSize(level) ),
ty = Math.floor( pixel.y / this.getTileSize(level) );
var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level) ),
tx = Math.floor( pixel.x / this.getTileWidth(level) ),
ty = Math.floor( pixel.y / this.getTileHeight(level) );
return new $.Point( tx, ty );
},
@ -308,11 +360,12 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
*/
getTileBounds: function( level, x, y ) {
var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
tileSize = this.getTileSize(level),
px = ( x === 0 ) ? 0 : tileSize * x - this.tileOverlap,
py = ( y === 0 ) ? 0 : tileSize * y - this.tileOverlap,
sx = tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
sy = tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
tileWidth = this.getTileWidth(level),
tileHeight = this.getTileHeight(level),
px = ( x === 0 ) ? 0 : tileWidth * x - this.tileOverlap,
py = ( y === 0 ) ? 0 : tileHeight * y - this.tileOverlap,
sx = tileWidth + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
sy = tileHeight + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
scale = 1.0 / dimensionsScaled.x;
sx = Math.min( sx, dimensionsScaled.x - px );
@ -560,8 +613,7 @@ function processResponse( xhr ){
data = xhr.responseText;
}
}else if( responseText.match(/\s*[\{\[].*/) ){
/*jshint evil:true*/
data = eval( '('+responseText+')' );
data = $.parseJSON(responseText);
}else{
data = responseText;
}

View File

@ -374,7 +374,6 @@ $.Viewer = function( options ) {
viewer: this,
viewport: this.viewport,
element: this.canvas,
opacity: this.opacity,
debugGridColor: this.debugGridColor
});
@ -1073,6 +1072,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
};
/**
* Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
* Note: the pre-full-screen event is not raised when the user is exiting
* full-screen mode by pressing the Esc key. In that case, consider using
* the full-screen, pre-full-page or full-page events.
*
* @event pre-full-screen
* @memberof OpenSeadragon.Viewer
@ -1191,6 +1193,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* named 'getTileUrl', it is treated as a custom TileSource.
* @param {Number} [options.index] The index of the item. Added on top of
* all other items if not specified.
* @param {Boolean} [options.replace=false] If true, the item at options.index will be
* removed and the new item is added in its place. options.tileSource will be
* interpreted and fetched if necessary before the old item is removed to avoid leaving
* a gap in the world.
* @param {Number} [options.x=0] The X position for the image in viewport coordinates.
* @param {Number} [options.y=0] The Y position for the image in viewport coordinates.
* @param {Number} [options.width=1] The width for the image in viewport coordinates.
@ -1198,6 +1204,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
* (portions of the image outside of this area will not be visible). Only works on
* browsers that support the HTML5 canvas.
* @param {Number} [options.opacity] Opacity the tiled image should be drawn at by default.
* @param {Function} [options.success] A function that gets called when the image is
* successfully added. It's passed the event object which contains a single property:
* "item", the resulting TiledImage.
@ -1206,17 +1213,31 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* and "source" properties.
* @param {Boolean} [options.collectionImmediately=false] If collectionMode is on,
* specifies whether to snap to the new arrangement immediately or to animate to it.
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @fires OpenSeadragon.World.event:add-item
* @fires OpenSeadragon.Viewer.event:add-item-failed
*/
addTiledImage: function( options ) {
$.console.assert(options, "[Viewer.addTiledImage] options is required");
$.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required");
$.console.assert(!options.replace || (options.index > -1 && options.index < this.world.getItemCount()),
"[Viewer.addTiledImage] if options.replace is used, options.index must be a valid index in Viewer.world");
var _this = this;
if (options.replace) {
options.replaceItem = _this.world.getItemAt(options.index);
}
this._hideMessage();
if (options.placeholderFillStyle === undefined) {
options.placeholderFillStyle = this.placeholderFillStyle;
}
if (options.opacity === undefined) {
options.opacity = this.opacity;
}
var myQueueItem = {
options: options
};
@ -1272,6 +1293,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this._loadQueue.splice(0, 1);
if (queueItem.options.replace) {
var newIndex = _this.world.getIndexOfItem(options.replaceItem);
if (newIndex != -1) {
options.index = newIndex;
}
_this.world.removeItem(options.replaceItem);
}
tiledImage = new $.TiledImage({
viewer: _this,
source: queueItem.tileSource,
@ -1284,6 +1313,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
width: queueItem.options.width,
height: queueItem.options.height,
clip: queueItem.options.clip,
placeholderFillStyle: queueItem.options.placeholderFillStyle,
opacity: queueItem.options.opacity,
springStiffness: _this.springStiffness,
animationTime: _this.animationTime,
minZoomImageRatio: _this.minZoomImageRatio,
@ -1305,6 +1336,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this.world.arrange({
immediately: queueItem.options.collectionImmediately,
rows: _this.collectionRows,
columns: _this.collectionColumns,
layout: _this.collectionLayout,
tileSize: _this.collectionTileSize,
tileMargin: _this.collectionTileMargin
@ -1697,7 +1729,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* is closed which include when changing page.
* @method
* @param {Element|String|Object} element - A reference to an element or an id for
* the element which will overlayed. Or an Object specifying the configuration for the overlay
* the element which will be overlayed. Or an Object specifying the configuration for the overlay
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the
@ -1757,6 +1789,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* Updates the overlay represented by the reference to the element or
* element id moving it to the new location, relative to the new placement.
* @method
* @param {Element|String} element - A reference to an element or an id for
* the element which is overlayed.
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the
@ -1980,8 +2014,7 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
if ( tileSource.match( /\s*<.*/ ) ) {
tileSource = $.parseXml( tileSource );
} else if ( tileSource.match( /\s*[\{\[].*/ ) ) {
/*jshint evil:true*/
tileSource = eval( '(' + tileSource + ')' );
tileSource = $.parseJSON(tileSource);
}
}
@ -2206,7 +2239,7 @@ function onCanvasKeyDown( event ) {
if ( event.shift ) {
this.viewport.zoomBy(1.1);
} else {
this.viewport.panBy(new $.Point(0, -0.05));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
}
this.viewport.applyConstraints();
return false;
@ -2214,16 +2247,16 @@ function onCanvasKeyDown( event ) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
} else {
this.viewport.panBy(new $.Point(0, 0.05));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
}
this.viewport.applyConstraints();
return false;
case 37://left arrow
this.viewport.panBy(new $.Point(-0.05, 0));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
this.viewport.applyConstraints();
return false;
case 39://right arrow
this.viewport.panBy(new $.Point(0.05, 0));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints();
return false;
default:
@ -2255,7 +2288,7 @@ function onCanvasKeyPress( event ) {
if ( event.shift ) {
this.viewport.zoomBy(1.1);
} else {
this.viewport.panBy(new $.Point(0, -0.05));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
}
this.viewport.applyConstraints();
return false;
@ -2264,16 +2297,16 @@ function onCanvasKeyPress( event ) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
} else {
this.viewport.panBy(new $.Point(0, 0.05));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
}
this.viewport.applyConstraints();
return false;
case 97://a
this.viewport.panBy(new $.Point(-0.05, 0));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
this.viewport.applyConstraints();
return false;
case 100://d
this.viewport.panBy(new $.Point(0.05, 0));
this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints();
return false;
default:
@ -2811,13 +2844,31 @@ function updateOnce( viewer ) {
return;
}
var containerSize;
if ( viewer.autoResize ) {
var containerSize = _getSafeElemSize( viewer.container );
containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
// maintain image position
var oldBounds = viewer.viewport.getBounds();
var oldCenter = viewer.viewport.getCenter();
resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
if ( viewer.preserveImageSizeOnResize ) {
var prevContainerSize = THIS[ viewer.hash ].prevContainerSize;
var bounds = viewer.viewport.getBounds(true);
var deltaX = (containerSize.x - prevContainerSize.x);
var deltaY = (containerSize.y - prevContainerSize.y);
var viewportDiff = viewer.viewport.deltaPointsFromPixels(new OpenSeadragon.Point(deltaX, deltaY), true);
viewer.viewport.resize(new OpenSeadragon.Point(containerSize.x, containerSize.y), false);
// Keep the center of the image in the center and just adjust the amount of image shown
bounds.width += viewportDiff.x;
bounds.height += viewportDiff.y;
bounds.x -= (viewportDiff.x / 2);
bounds.y -= (viewportDiff.y / 2);
viewer.viewport.fitBoundsWithConstraints(bounds, true);
}
else {
// maintain image position
var oldBounds = viewer.viewport.getBounds();
var oldCenter = viewer.viewport.getCenter();
resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
}
THIS[ viewer.hash ].prevContainerSize = containerSize;
THIS[ viewer.hash ].forceRedraw = true;
}
@ -2914,19 +2965,15 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter
viewport.resize( containerSize, true );
// We try to remove blanks as much as possible
var worldBounds = viewer.world.getHomeBounds();
var newWidth = oldBounds.width <= worldBounds.width ? oldBounds.width : worldBounds.width;
var newHeight = oldBounds.height <= worldBounds.height ?
oldBounds.height : worldBounds.height;
var newBounds = new $.Rect(
oldCenter.x - ( newWidth / 2.0 ),
oldCenter.y - ( newHeight / 2.0 ),
newWidth,
newHeight
);
viewport.fitBounds( newBounds, true );
oldCenter.x - ( oldBounds.width / 2.0 ),
oldCenter.y - ( oldBounds.height / 2.0 ),
oldBounds.width,
oldBounds.height
);
// let the viewport decide if the bounds are too big or too small
viewport.fitBoundsWithConstraints( newBounds, true );
}
function drawWorld( viewer ) {

View File

@ -281,6 +281,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.
* @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.
* @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.
* @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.
* @fires OpenSeadragon.World.event:metrics-change
@ -290,10 +291,16 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
var immediately = options.immediately || false;
var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;
var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;
var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;
var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;
var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;
var increment = tileSize + tileMargin;
var wrap = Math.ceil(this._items.length / rows);
var wrap;
if (!options.rows && columns) {
wrap = columns;
} else {
wrap = Math.ceil(this._items.length / rows);
}
var x = 0;
var y = 0;
var item, box, width, height, position;

View File

@ -71,6 +71,7 @@
<script src="/test/modules/tiledimage.js"></script>
<script src="/test/modules/tilecache.js"></script>
<script src="/test/modules/referencestrip.js"></script>
<script src="/test/modules/tilesource.js"></script>
<script src="/test/modules/tilesourcecollection.js"></script>
<script src="/test/modules/spring.js"></script>
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up)

View File

@ -1,21 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Basic Demo</title>
<title>OpenSeadragon Coordinates Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
Simple demo page to show a default OpenSeadragon viewer.
Simple demo page to show OpenSeadragon coordinates system.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<div>
@ -24,16 +24,26 @@
<th></th>
<th>Window (pixel)</th>
<th>Container (pixel)</th>
<th>Image (pixel)</th>
<th>Image 1 - top left (pixel)</th>
<th>Image 2 - bottom right (pixel)</th>
<th>Viewport (point)</th>
</tr>
<tr>
<th>Cursor position</th>
<td id="windowPosition"></td>
<td id="containerPosition"></td>
<td id="imagePosition"></td>
<td id="image1Position"></td>
<td id="image2Position"></td>
<td id="viewportPosition"></td>
</tr>
<tr>
<th>Zoom</th>
<td>-</td>
<td>-</td>
<td id="image1Zoom"></td>
<td id="image2Zoom"></td>
<td id="viewportZoom"></td>
</tr>
</table>
</div>
<script type="text/javascript">
@ -42,36 +52,57 @@
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
showNavigator:true
tileSources: [{
tileSource: "../data/testpattern.dzi"
},
{
tileSource: "../data/testpattern.dzi",
x: 1,
y: 1,
width: 0.5
}
],
showNavigator: true
});
function pointToString(point) {
return point.x.toPrecision(4) + "," + point.y.toPrecision(4);
}
var onMouseTrackerMove = function(eventSender, eventData) {
var viewerX = eventData.position.x;
var viewerY = eventData.position.y;
var onMouseTrackerMove = function (event) {
var viewerX = event.position.x;
var viewerY = event.position.y;
var windowPoint = new OpenSeadragon.Point(viewerX, viewerY);
$("#windowPosition").text( pointToString(windowPoint));
$("#windowPosition").text(pointToString(windowPoint));
var containerPoint = windowPoint.minus(
OpenSeadragon.getElementPosition(viewer.element));
$("#containerPosition").text( pointToString(containerPoint));
var imagePoint = viewer.viewport.windowToImageCoordinates(windowPoint);
$("#imagePosition").text( pointToString(imagePoint));
$("#containerPosition").text(pointToString(containerPoint));
var image1 = viewer.world.getItemAt(0);
var imagePoint = image1.windowToImageCoordinates(windowPoint);
$("#image1Position").text(pointToString(imagePoint));
var image2 = viewer.world.getItemAt(1);
var imagePoint = image2.windowToImageCoordinates(windowPoint);
$("#image2Position").text(pointToString(imagePoint));
var viewportPoint = viewer.viewport.windowToViewportCoordinates(windowPoint);
$("#viewportPosition").text( pointToString(viewportPoint));
$("#viewportPosition").text(pointToString(viewportPoint));
};
mouseTracker = new OpenSeadragon.MouseTracker({
element: document,
moveHandler: onMouseTrackerMove
}).setTracking(true);
function onAnimation() {
var viewportZoom = viewer.viewport.getZoom(true);
$("#viewportZoom").text(viewportZoom.toFixed(3));
var image1 = viewer.world.getItemAt(0);
var image1Zoom = image1.viewportToImageZoom(viewportZoom);
$("#image1Zoom").text(image1Zoom.toFixed(3));
var image2 = viewer.world.getItemAt(1);
var image2Zoom = image2.viewportToImageZoom(viewportZoom);
$("#image2Zoom").text(image2Zoom.toFixed(3));
}
viewer.addHandler("animation", onAnimation);
</script>
</body>
</html>

View File

@ -464,18 +464,22 @@
var page = this.pages[this.pageIndex];
var box = page.getBounds();
this.highlight
.style('opacity', 1)
.attr("x", box.x)
.attr("width", box.width)
.attr("y", box.y)
.attr("height", box.height);
if (this.highlight) {
this.highlight
.style('opacity', 1)
.attr("x", box.x)
.attr("width", box.width)
.attr("y", box.y)
.attr("height", box.height);
}
},
// ----------
updateHover: function(pageIndex) {
if (pageIndex === -1 || this.mode !== 'thumbs') {
this.hover.style('opacity', 0);
if (this.hover) {
this.hover.style('opacity', 0);
}
this.$scrollCover.css({
'cursor': 'default'
});
@ -490,12 +494,14 @@
var page = this.pages[pageIndex];
var box = page.getBounds();
this.hover
.style('opacity', 0.3)
.attr("x", box.x)
.attr("width", box.width)
.attr("y", box.y)
.attr("height", box.height);
if (this.hover) {
this.hover
.style('opacity', 0.3)
.attr("x", box.x)
.attr("width", box.width)
.attr("y", box.y)
.attr("height", box.height);
}
},
// ----------

View File

@ -23,19 +23,19 @@
<script type="text/javascript">
var _viewer;
var generateUniqueHash = (function() {
var counter = 0;
return function() {
return "openseadragon_" + (counter++);
};
})();
function createViewer() {
if ( _viewer ) {
destroyViewer();
}
_viewer = OpenSeadragon({
element: document.getElementById("contentDiv"),
showNavigationControl: false,
@ -44,7 +44,7 @@
tileSources: "../data/testpattern.dzi"
});
}
function destroyViewer() {
if ( _viewer ) {
_viewer.destroy();

41
test/demo/overlay.html Normal file
View File

@ -0,0 +1,41 @@
<html>
<title>OpenSeadragon Overlay Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div id="contentDiv" class="openseadragon1"></div>
<div id="annotation-div">
<input type="button" value="Hide Overlay" id="hideOverlay">
<script type="text/javascript">
var viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
});
viewer.addHandler("open", function(event) {
var elt = document.createElement("div");
elt.style.background = "green";
elt.id = "runtime-overlay";
elt.style.border = "1px solid red";
viewer.addOverlay( elt, new OpenSeadragon.Rect(0.2, 0.2, 0.75, 0.75) );
});
$("#hideOverlay").click(function(){
$("#runtime-overlay").toggle();
});
</script>
</body>
</html>

View File

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<title>TileSource Swapping</title>
<script type="text/javascript" src="../../build/openseadragon/openseadragon.js"></script>
<script type="text/javascript" src="../lib/jquery-1.9.1.min.js"></script>
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
.viewer-position {
position: absolute;
left: 0;
top: 30px;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<div>This is a demo of using a single image stand-in and then swapping to a full TileSource on zooming. Click the image to see it in action.</div>
<div id="openseadragon1" class="viewer-position"></div>
<script>
var duomoStandin = {
type: 'legacy-image-pyramid',
levels: [
{
url: 'http://openseadragon.github.io/example-images/duomo/duomo_files/8/0_0.jpg',
width: 218,
height: 160
}
]
};
var duomo = {
Image: {
xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
Url: 'http://openseadragon.github.io/example-images/duomo/duomo_files/',
Format: 'jpg',
Overlap: '2',
TileSize: '256',
Size: {
Width: '13920',
Height: '10200'
}
}
};
var viewer = OpenSeadragon({
id: 'openseadragon1',
prefixUrl: '../../build/openseadragon/images/',
tileSources: duomoStandin,
minZoomImageRatio: 0.1,
defaultZoomLevel: 0.1,
zoomPerClick: 1
});
viewer.addHandler('canvas-click', function(event) {
if (event.quick) {
var standin = viewer.world.getItemAt(0);
var standinBounds = standin.getBounds();
viewer.viewport.fitBounds(standinBounds);
viewer.addTiledImage({
x: standinBounds.x,
y: standinBounds.y,
width: standinBounds.width,
tileSource: duomo,
index: 0, // Add the new image below the stand-in.
success: function(event) {
var fullImage = event.item;
// The changeover will look better if we wait for the first tile to be drawn.
var tileDrawnHandler = function(event) {
if (event.tiledImage === fullImage) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
viewer.world.removeItem(standin);
}
};
viewer.addHandler('tile-drawn', tileDrawnHandler);
}
});
}
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -33,9 +33,6 @@
createViewer();
ok(viewer.drawer, 'Drawer exists');
equal(viewer.drawer.canRotate(), OpenSeadragon.supportsCanvas, 'we can rotate if we have canvas');
equal(viewer.drawer.getOpacity(), 1, 'starts with full opacity');
viewer.drawer.setOpacity(0.4);
equal(viewer.drawer.getOpacity(), 0.4, 'setting opacity works');
start();
});
@ -67,18 +64,64 @@
});
});
// ----------
asyncTest('sketchCanvas', function() {
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
var drawer = viewer.drawer;
viewer.addHandler('tile-drawn', function noOpacityHandler() {
viewer.removeHandler('tile-drawn', noOpacityHandler);
equal(drawer.sketchCanvas, null,
'The sketch canvas should be null if no decimal opacity is used.');
equal(drawer.sketchContext, null,
'The sketch context should be null if no decimal opacity is used.');
testOpacityDecimal();
});
function testOpacityDecimal() {
var tiledImage;
viewer.addTiledImage({
tileSource: '/test/data/testpattern.dzi',
opacity: 0.5,
success: function(event) {
tiledImage = event.item;
}
});
viewer.addHandler('tile-drawn', function opacityDecimalHandler(event) {
if (tiledImage !== event.tiledImage) {
return;
}
viewer.removeHandler('tile-drawn', opacityDecimalHandler);
notEqual(drawer.sketchCanvas, null,
'The sketch canvas should not be null once a decimal opacity has been used.');
notEqual(drawer.sketchContext, null,
'The sketch context should not be null once a decimal opacity has been used.');
start();
});
}
});
// ----------
asyncTest('deprecations', function() {
createViewer();
Util.testDeprecation(viewer.drawer, 'addOverlay', viewer, 'addOverlay');
Util.testDeprecation(viewer.drawer, 'updateOverlay', viewer, 'updateOverlay');
Util.testDeprecation(viewer.drawer, 'removeOverlay', viewer, 'removeOverlay');
Util.testDeprecation(viewer.drawer, 'clearOverlays', viewer, 'clearOverlays');
Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw');
Util.testDeprecation(viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded');
Util.testDeprecation(viewer.drawer, 'reset', viewer.world, 'resetItems');
Util.testDeprecation(viewer.drawer, 'update', viewer.world, 'draw');
start();
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
viewer.world.addHandler('add-item', function() {
Util.testDeprecation(viewer.drawer, 'addOverlay', viewer, 'addOverlay');
Util.testDeprecation(viewer.drawer, 'updateOverlay', viewer, 'updateOverlay');
Util.testDeprecation(viewer.drawer, 'removeOverlay', viewer, 'removeOverlay');
Util.testDeprecation(viewer.drawer, 'clearOverlays', viewer, 'clearOverlays');
Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw');
Util.testDeprecation(viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded');
Util.testDeprecation(viewer.drawer, 'reset', viewer.world, 'resetItems');
Util.testDeprecation(viewer.drawer, 'update', viewer.world, 'draw');
Util.testDeprecation(viewer.drawer, 'setOpacity', viewer.world.getItemAt(0), 'setOpacity');
Util.testDeprecation(viewer.drawer, 'getOpacity', viewer.world.getItemAt(0), 'getOpacity');
start();
});
});
})();

View File

@ -968,4 +968,103 @@
viewer.open( '/test/data/testpattern.dzi' );
} );
// tile-loaded event tests
asyncTest( 'Viewer: tile-loaded event without callback.', function () {
function tileLoaded ( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
var tile = event.tile;
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
setTimeout(function() {
notOk( tile.loading, "The tile should not be marked as loading.");
ok( tile.loaded, "The tile should be marked as loaded.");
start();
}, 0);
}
viewer.addHandler( 'tile-loaded', tileLoaded);
viewer.open( '/test/data/testpattern.dzi' );
} );
asyncTest( 'Viewer: tile-loaded event with 1 callback.', function () {
function tileLoaded ( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
var tile = event.tile;
var callback = event.getCompletionCallback();
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
ok( callback, "The event should have a callback.");
setTimeout(function() {
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
callback();
notOk( tile.loading, "The tile should not be marked as loading.");
ok( tile.loaded, "The tile should be marked as loaded.");
start();
}, 0);
}
viewer.addHandler( 'tile-loaded', tileLoaded);
viewer.open( '/test/data/testpattern.dzi' );
} );
asyncTest( 'Viewer: tile-loaded event with 2 callbacks.', function () {
function tileLoaded ( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
var tile = event.tile;
var callback1 = event.getCompletionCallback();
var callback2 = event.getCompletionCallback();
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
setTimeout(function() {
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
callback1();
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
setTimeout(function() {
ok( tile.loading, "The tile should be marked as loading.");
notOk( tile.loaded, "The tile should not be marked as loaded.");
callback2();
notOk( tile.loading, "The tile should not be marked as loading.");
ok( tile.loaded, "The tile should be marked as loaded.");
start();
}, 0);
}, 0);
}
viewer.addHandler( 'tile-loaded', tileLoaded);
viewer.open( '/test/data/testpattern.dzi' );
} );
asyncTest( 'Viewer: tile-unloaded event.', function() {
var tiledImage;
var tile;
function tileLoaded( event ) {
viewer.removeHandler( 'tile-loaded', tileLoaded);
tiledImage = event.tiledImage;
tile = event.tile;
setTimeout(function() {
tiledImage.reset();
}, 0);
}
function tileUnloaded( event ) {
viewer.removeHandler( 'tile-unloaded', tileUnloaded );
equal( tile, event.tile,
"The unloaded tile should be the same than the loaded one." );
equal( tiledImage, event.tiledImage,
"The tiledImage of the unloaded tile should be the same than the one of the loaded one." );
start();
}
viewer.addHandler( 'tile-loaded', tileLoaded );
viewer.addHandler( 'tile-unloaded', tileUnloaded );
viewer.open( '/test/data/testpattern.dzi' );
} );
} )();

View File

@ -14,14 +14,18 @@
var viewer = null;
// ----------
var testOpen = function(name) {
var testOpenUrl = function(relativeUrl) {
testOpen('/test/data/' + relativeUrl);
};
var testOpen = function(tileSource) {
$(document).ready(function() {
var timeWatcher = Util.timeWatcher(7000);
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/' + name
tileSources: tileSource
});
ok(viewer, 'Viewer exists');
@ -52,62 +56,90 @@
// ----------
asyncTest('DZI', function() {
testOpen('testpattern.dzi');
testOpenUrl('testpattern.dzi');
});
// ----------
asyncTest('DZI JSONp', function() {
testOpen('testpattern.js');
testOpenUrl('testpattern.js');
});
// ----------
asyncTest('DZI XML', function() {
testOpen('testpattern.xml');
testOpenUrl('testpattern.xml');
});
// ----------
asyncTest('DZI XML with query parameter', function() {
testOpen('testpattern.xml?param=value');
testOpenUrl('testpattern.xml?param=value');
});
// ----------
asyncTest('IIIF 1.0 JSON', function() {
testOpen('iiif_1_0_files/info.json');
testOpenUrl('iiif_1_0_files/info.json');
});
// ----------
asyncTest('IIIF 1.0 XML', function() {
testOpen('iiif_1_0_files/info.xml');
testOpenUrl('iiif_1_0_files/info.xml');
});
// ----------
asyncTest('IIIF 1.1 JSON', function() {
testOpen('iiif_1_1_tiled/info.json');
testOpenUrl('iiif_1_1_tiled/info.json');
});
// ----------
asyncTest('IIIF No Tiles, Less than 256', function() {
testOpen('iiif_1_1_no_tiles_255/info.json');
testOpenUrl('iiif_1_1_no_tiles_255/info.json');
});
// ----------
asyncTest('IIIF No Tiles, Bet. 256 and 512', function() {
testOpen('iiif_1_1_no_tiles_384/info.json');
testOpenUrl('iiif_1_1_no_tiles_384/info.json');
});
// ----------
asyncTest('IIIF No Tiles, Bet. 512 and 1024', function() {
testOpen('iiif_1_1_no_tiles_768/info.json');
testOpenUrl('iiif_1_1_no_tiles_768/info.json');
});
// ----------
asyncTest('IIIF No Tiles, Larger than 1024', function() {
testOpen('iiif_1_1_no_tiles_1048/info.json');
testOpenUrl('iiif_1_1_no_tiles_1048/info.json');
});
// ----------
asyncTest('IIIF 2.0 JSON', function() {
testOpen('iiif_2_0_tiled/info.json');
});
testOpenUrl('iiif_2_0_tiled/info.json');
});
// ----------
asyncTest('IIIF 2.0 JSON String', function() {
testOpen(
'{' +
' "@context": "http://iiif.io/api/image/2/context.json",' +
' "@id": "http://localhost:8000/test/data/iiif_2_0_tiled",' +
' "protocol": "http://iiif.io/api/image",' +
' "height": 1024,' +
' "width": 775,' +
' "tiles" : [{"width":256, "scaleFactors":[1,2,4,8]}],' +
' "profile": ["http://iiif.io/api/image/2/level1.json",' +
' {' +
' "qualities": [' +
' "native",' +
' "bitonal",' +
' "grey",' +
' "color"' +
' ],' +
' "formats": [' +
' "jpg",' +
' "png",' +
' "gif"' +
' ]' +
' }' +
' ]' +
'}');
});
})();

View File

@ -27,7 +27,7 @@
// ----------
asyncTest( 'Multi-image operations', function() {
expect( 21 );
expect( 24 );
viewer.addHandler( "open", function( ) {
equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." );
@ -95,22 +95,36 @@
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 );
options.index = 2;
options.replace = true;
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function replaceAddItemHandler( event ) {
viewer.world.removeHandler( "add-item", replaceAddItemHandler );
var item4 = event.item;
equal( viewer.world.getItemCount( ), 4,
"4 items should still be present after replacing the second item." );
equal( viewer.world.getIndexOfItem( item4 ), 2,
"Item 4 should be added with index 2." );
equal( viewer.world.getIndexOfItem( item3 ), -1,
"Item 3 should be at index -1." );
equal( item2, event.item, "Removed item should be item2." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
viewer.world.removeHandler( "remove-item", removeItemHandler );
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." );
equal( item2, event.item, "Removed item should be item2." );
start();
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( item4 ), 1,
"Item 4 should be at index 1." );
start();
});
viewer.world.removeItem( item2 );
});
viewer.world.removeItem( item2 );
});
});
});

View File

@ -13,8 +13,15 @@
// ----------
asyncTest('basics', function() {
var fakeTiledImage0 = {};
var fakeTiledImage1 = {};
var fakeViewer = {
raiseEvent: function() {}
};
var fakeTiledImage0 = {
viewer: fakeViewer
};
var fakeTiledImage1 = {
viewer: fakeViewer
};
var fakeTile0 = {
url: 'foo.jpg',
@ -58,7 +65,12 @@
// ----------
asyncTest('maxImageCacheCount', function() {
var fakeTiledImage0 = {};
var fakeViewer = {
raiseEvent: function() {}
};
var fakeTiledImage0 = {
viewer: fakeViewer
};
var fakeTile0 = {
url: 'different.jpg',

View File

@ -220,4 +220,41 @@
});
});
// ----------
asyncTest('opacity', function() {
function testDefaultOpacity() {
viewer.removeHandler('open', testDefaultOpacity);
var image = viewer.world.getItemAt(0);
strictEqual(image.getOpacity(), 0.5, 'image has default opacity');
image.setOpacity(1);
strictEqual(image.getOpacity(), 1, 'opacity is set correctly');
viewer.addHandler('open', testTileSourceOpacity);
viewer.open({
tileSource: '/test/data/testpattern.dzi',
opacity: 0.25
});
}
function testTileSourceOpacity() {
viewer.removeHandler('open', testTileSourceOpacity);
var image = viewer.world.getItemAt(0);
strictEqual(image.getOpacity(), 0.25, 'image has correct opacity');
image.setOpacity(0);
strictEqual(image.getOpacity(), 0, 'opacity is set correctly');
start();
}
viewer.addHandler('open', testDefaultOpacity);
viewer.opacity = 0.5;
viewer.open({
tileSource: '/test/data/testpattern.dzi',
});
});
})();

View File

@ -0,0 +1,51 @@
/* global module, ok, equal, start, test, testLog, Util */
(function() {
module('TileSource', {
setup: function() {
testLog.reset();
}
});
test("should set sane tile size defaults", function() {
var source = new OpenSeadragon.TileSource();
equal(source.getTileWidth(), 0, "getTileWidth() should return 0 if not provided a size");
equal(source.getTileHeight(), 0, "getTileHeight() should return 0 if not provided a size");
});
test("providing tileSize", function(){
var tileSize = 256,
source = new OpenSeadragon.TileSource({
tileSize: tileSize
});
equal(source.tileSize, undefined, "tileSize should not be set on the tileSource");
equal(source.getTileWidth(), tileSize, "getTileWidth() should equal tileSize");
equal(source.getTileHeight(), tileSize, "getTileHeight() should equal tileSize");
});
test("providing tileWidth and tileHeight", function(){
var tileWidth = 256,
tileHeight = 512,
source = new OpenSeadragon.TileSource({
tileWidth: tileWidth,
tileHeight: tileHeight
});
equal(source._tileWidth, tileWidth, "tileWidth option should set _tileWidth");
equal(source._tileHeight, tileHeight, "tileHeight option should set _tileHeight");
equal(source.tileWidth, undefined, "tileWidth should be renamed _tileWidth");
equal(source.tileHeight, undefined, "tileHeight should be renamed _tileHeight");
equal(source.getTileWidth(), tileWidth, "getTileWidth() should equal tileWidth");
equal(source.getTileHeight(), tileHeight, "getTileHeight() should equal tileHeight");
});
test('getTileSize() deprecation', function() {
var source = new OpenSeadragon.TileSource();
Util.testDeprecation(source, 'getTileSize');
});
}());

View File

@ -1,6 +1,6 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
(function () {
var viewer;
module('Units', {
@ -10,8 +10,8 @@
testLog.reset();
viewer = OpenSeadragon({
id: 'unitsexample',
prefixUrl: '/build/openseadragon/images/',
id: 'unitsexample',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
@ -30,38 +30,57 @@
Util.assessNumericValue(a.y, b.y, 0.00000001, message);
}
// ----------
asyncTest('Coordinates conversions', function() {
// Check that f^-1 ( f(x) ) = x
function checkPoint(context) {
var viewport = viewer.viewport;
function checkPoint(context) {
var viewport = viewer.viewport;
var point = new OpenSeadragon.Point(15, 12);
var result = viewport.viewerElementToImageCoordinates(
var point = new OpenSeadragon.Point(15, 12);
var result = viewport.viewerElementToImageCoordinates(
viewport.imageToViewerElementCoordinates(point));
pointEqual(result, point, 'viewerElement and image ' + context);
pointEqual(result, point, 'viewerElement and image ' + context);
var result = viewport.windowToImageCoordinates(
result = viewport.windowToImageCoordinates(
viewport.imageToWindowCoordinates(point));
pointEqual(result, point, 'window and image ' + context);
pointEqual(result, point, 'window and image ' + context);
var result = viewport.viewerElementToViewportCoordinates(
result = viewport.viewerElementToViewportCoordinates(
viewport.viewportToViewerElementCoordinates(point));
pointEqual(result, point, 'viewerElement and viewport ' + context);
pointEqual(result, point, 'viewerElement and viewport ' + context);
var result = viewport.windowToViewportCoordinates(
result = viewport.windowToViewportCoordinates(
viewport.viewportToWindowCoordinates(point));
pointEqual(result, point, 'window and viewport ' + context);
pointEqual(result, point, 'window and viewport ' + context);
for (var i = 0; i < viewer.world.getItemCount(); i++) {
var tiledImage = viewer.world.getItemAt(i);
result = tiledImage.viewportToImageCoordinates(
tiledImage.imageToViewportCoordinates(point));
pointEqual(result, point, 'viewport and tiled image ' + i + context);
result = tiledImage.viewerElementToImageCoordinates(
tiledImage.imageToViewerElementCoordinates(point));
pointEqual(result, point, 'viewerElement and tiled image ' + i + context);
result = tiledImage.windowToImageCoordinates(
tiledImage.imageToWindowCoordinates(point));
pointEqual(result, point, 'window and tiled image ' + i + context);
}
}
// ----------
asyncTest('Single image coordinates conversions', function () {
viewer.addHandler("open", function () {
var viewport = viewer.viewport;
var tiledImage = viewer.world.getItemAt(0);
var point0_0 = new OpenSeadragon.Point(0, 0);
var point = viewport.viewerElementToViewportCoordinates(point0_0);
pointEqual(point, point0_0, 'When opening, viewer coordinate 0,0 is also point 0,0');
var pixel = viewport.viewerElementToImageCoordinates(point0_0);
pointEqual(pixel, point0_0, 'When opening, viewer coordinate 0,0 is also pixel 0,0');
var viewportPixel = viewport.viewerElementToImageCoordinates(point0_0);
pointEqual(viewportPixel, point0_0, 'When opening, viewer coordinate 0,0 is also viewport pixel 0,0');
var imagePixel = tiledImage.viewerElementToImageCoordinates(point0_0);
pointEqual(imagePixel, point0_0, 'When opening, viewer coordinate 0,0 is also image pixel 0,0');
var viewerWidth = $(viewer.element).width();
var imageWidth = viewer.source.dimensions.x;
@ -69,15 +88,17 @@
var viewerTopRight = new OpenSeadragon.Point(viewerWidth, 0);
var imageTopRight = new OpenSeadragon.Point(imageWidth, 0);
var point = viewport.viewerElementToViewportCoordinates(viewerTopRight);
point = viewport.viewerElementToViewportCoordinates(viewerTopRight);
pointEqual(point, point1_0, 'Viewer top right has viewport coordinates 1,0.');
var pixel = viewport.viewerElementToImageCoordinates(viewerTopRight);
pointEqual(pixel, imageTopRight, 'Viewer top right has viewport coordinates imageWidth,0.');
viewportPixel = viewport.viewerElementToImageCoordinates(viewerTopRight);
pointEqual(viewportPixel, imageTopRight, 'Viewer top right has viewport pixel coordinates imageWidth,0.');
imagePixel = tiledImage.viewerElementToImageCoordinates(viewerTopRight);
pointEqual(imagePixel, imageTopRight, 'Viewer top right has image pixel coordinates imageWidth,0.');
checkPoint('after opening');
checkPoint(' after opening');
viewer.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler);
checkPoint('after zoom and pan');
checkPoint(' after zoom and pan');
start();
});
viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2));
@ -85,19 +106,80 @@
viewer.open('/test/data/testpattern.dzi');
});
// ---------
asyncTest('Multiple images coordinates conversion', function () {
viewer.addHandler("open", function () {
var viewport = viewer.viewport;
var tiledImage1 = viewer.world.getItemAt(0);
var tiledImage2 = viewer.world.getItemAt(1);
var imageWidth = viewer.source.dimensions.x;
var imageHeight = viewer.source.dimensions.y;
var point0_0 = new OpenSeadragon.Point(0, 0);
var point = viewport.viewerElementToViewportCoordinates(point0_0);
pointEqual(point, point0_0, 'When opening, viewer coordinate 0,0 is also point 0,0');
var image1Pixel = tiledImage1.viewerElementToImageCoordinates(point0_0);
pointEqual(image1Pixel, point0_0, 'When opening, viewer coordinate 0,0 is also image 1 pixel 0,0');
var image2Pixel = tiledImage2.viewerElementToImageCoordinates(point0_0);
pointEqual(image2Pixel,
new OpenSeadragon.Point(-2 * imageWidth, -2 * imageHeight),
'When opening, viewer coordinates 0,0 is also image 2 pixel -2*imageWidth, -2*imageHeight');
var viewerWidth = $(viewer.element).width();
var viewerHeight = $(viewer.element).height();
var viewerBottomRight = new OpenSeadragon.Point(viewerWidth, viewerHeight);
point = viewport.viewerElementToViewportCoordinates(viewerBottomRight);
pointEqual(point, new OpenSeadragon.Point(1.5, 1.5),
'Viewer bottom right has viewport coordinates 1.5,1.5.');
image1Pixel = tiledImage1.viewerElementToImageCoordinates(viewerBottomRight);
pointEqual(image1Pixel,
new OpenSeadragon.Point(imageWidth * 1.5, imageHeight * 1.5),
'Viewer bottom right has image 1 pixel coordinates imageWidth * 1.5, imageHeight * 1.5');
image2Pixel = tiledImage2.viewerElementToImageCoordinates(viewerBottomRight);
pointEqual(image2Pixel,
new OpenSeadragon.Point(imageWidth, imageHeight),
'Viewer bottom right has image 2 pixel coordinates imageWidth,imageHeight.');
checkPoint(' after opening');
viewer.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler);
checkPoint(' after zoom and pan');
start();
});
viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2));
start();
});
viewer.open([{
tileSource: "/test/data/testpattern.dzi"
}, {
tileSource: "/test/data/testpattern.dzi",
x: 1,
y: 1,
width: 0.5
}
]);
});
// ----------
asyncTest('ZoomRatio', function() {
asyncTest('ZoomRatio 1 image', function () {
viewer.addHandler("open", function () {
var viewport = viewer.viewport;
var imageWidth = 1000;
var imageWidth = viewer.source.dimensions.x;
function getCurrentImageWidth() {
return viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(1, 0)).minus(
new OpenSeadragon.Point(1, 0)).minus(
viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0, 0))).x;
new OpenSeadragon.Point(0, 0))).x;
}
function checkZoom() {
@ -105,16 +187,16 @@
var expectedImageZoom = currentImageWidth / imageWidth;
var expectedViewportZoom = viewport.getZoom(true);
var actualImageZoom = viewport.viewportToImageZoom(
expectedViewportZoom);
expectedViewportZoom);
equal(actualImageZoom, expectedImageZoom);
var actualViewportZoom = viewport.imageToViewportZoom(actualImageZoom);
equal(actualViewportZoom, expectedViewportZoom);
}
checkZoom();
var zoomHandler = function() {
var zoomHandler = function () {
viewer.removeHandler('animation-finish', zoomHandler);
checkZoom();
start();
@ -127,5 +209,61 @@
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('ZoomRatio 2 images', function () {
viewer.addHandler("open", function () {
var viewport = viewer.viewport;
var imageWidth = viewer.source.dimensions.x;
var image1 = viewer.world.getItemAt(0);
var image2 = viewer.world.getItemAt(1);
function getCurrentImageWidth(image) {
var bounds = image.getBounds();
return viewport.viewportToViewerElementCoordinates(
bounds.getTopRight()).minus(
viewport.viewportToViewerElementCoordinates(
bounds.getTopLeft())).x;
}
function checkZoom(image) {
var currentImageWidth = getCurrentImageWidth(image);
var expectedImageZoom = currentImageWidth / imageWidth;
var expectedViewportZoom = viewport.getZoom(true);
var actualImageZoom = image.viewportToImageZoom(
expectedViewportZoom);
Util.assessNumericValue(actualImageZoom, expectedImageZoom,
0.00000001);
var actualViewportImage1Zoom = image.imageToViewportZoom(actualImageZoom);
Util.assessNumericValue(
actualViewportImage1Zoom, expectedViewportZoom, 0.00000001);
}
checkZoom(image1);
checkZoom(image2);
var zoomHandler = function () {
viewer.removeHandler('animation-finish', zoomHandler);
checkZoom(image1);
checkZoom(image2);
start();
};
viewer.addHandler('animation-finish', zoomHandler);
viewport.zoomTo(2);
});
viewer.open([{
tileSource: "/test/data/testpattern.dzi"
}, {
tileSource: "/test/data/testpattern.dzi",
x: 1,
y: 1,
width: 0.5
}
]);
});
})();

View File

@ -213,6 +213,26 @@
checkBounds(new OpenSeadragon.Rect(0, 0, 1, 4), 'one vertical column');
viewer.world.arrange({
layout: 'horizontal',
rows: false,
columns: 3,
tileSize: 1,
tileMargin: 0.5
});
checkBounds(new OpenSeadragon.Rect(0, 0, 4, 1), 'three horizontal columns (one horizontal row)');
viewer.world.arrange({
layout: 'vertical',
rows: false,
columns: 3,
tileSize: 1,
tileMargin: 0.5
});
checkBounds(new OpenSeadragon.Rect(0, 0, 1, 4), 'three vertical rows (one vertical column)');
start();
});

View File

@ -36,6 +36,7 @@
<script src="/test/modules/tiledimage.js"></script>
<script src="/test/modules/tilecache.js"></script>
<script src="/test/modules/referencestrip.js"></script>
<script src="/test/modules/tilesource.js"></script>
<script src="/test/modules/tilesourcecollection.js"></script>
<script src="/test/modules/spring.js"></script>
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up)