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, "browser": true,
"curly": true, "curly": true,
"eqeqeq": false, "eqeqeq": false,
"loopfunc": false, "loopfunc": false,
"noarg": true, "noarg": true,
"trailing": true, "trailing": true,
"undef": true, "undef": true,
"unused": false, "unused": false,
"globals": { "globals": {
"OpenSeadragon": true "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! 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: 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 ## 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) [![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 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) * 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 * 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 * Viewport.open supports positioning config properties
* For multi-image open, drawing isn't started until all tileSources have been opened * 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) * 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) * 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 * Rect and Point toString() functions are now consistent: rounding values to nearest hundredth
* Overlays appear in the DOM immediately on open or addOverlay (#507) * Overlays appear in the DOM immediately on open or addOverlay (#507)
@ -57,6 +80,8 @@ OPENSEADRAGON CHANGELOG
* Fixed: Cross Origin policy not working (#613) * Fixed: Cross Origin policy not working (#613)
* Optimized tile loading by clearing the queue on a re-draw when imageLoaderLimit is set (#616) * Optimized tile loading by clearing the queue on a re-draw when imageLoaderLimit is set (#616)
* Now animating zoom spring exponentially (#631) * 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: 1.2.1:

View File

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

View File

@ -68,7 +68,7 @@ $.ButtonGroup = function( options ) {
*/ */
this.element = options.element || $.makeNeutralElement( "div" ); 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 ){ if( !options.group ){
this.label = $.makeNeutralElement( "label" ); this.label = $.makeNeutralElement( "label" );
//TODO: support labels for ButtonGroups //TODO: support labels for ButtonGroups

View File

@ -42,11 +42,9 @@
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer. * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport. * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element. * @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. * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/ */
$.Drawer = function( options ) { $.Drawer = function( options ) {
var _this = this;
$.console.assert( options.viewer, "[Drawer] options.viewer is required" ); $.console.assert( options.viewer, "[Drawer] options.viewer is required" );
@ -72,7 +70,9 @@ $.Drawer = function( options ) {
this.viewer = options.viewer; this.viewer = options.viewer;
this.viewport = options.viewport; this.viewport = options.viewport;
this.debugGridColor = options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor; 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 ); 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; 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 * @member {Element} element
* @memberof OpenSeadragon.Drawer# * @memberof OpenSeadragon.Drawer#
@ -160,8 +167,11 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* @return {OpenSeadragon.Drawer} Chainable. * @return {OpenSeadragon.Drawer} Chainable.
*/ */
setOpacity: function( opacity ) { setOpacity: function( opacity ) {
this.opacity = opacity; $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
$.setElementOpacity( this.canvas, this.opacity, true ); var world = this.viewer.world;
for (var i = 0; i < world.getItemCount(); i++) {
world.getItemAt( i ).setOpacity( opacity );
}
return this; return this;
}, },
@ -170,7 +180,16 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* @returns {Number} * @returns {Number}
*/ */
getOpacity: function() { 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 // 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) //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
this.canvas.width = 1; this.canvas.width = 1;
this.canvas.height = 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.height != viewportSize.y ) {
this.canvas.width = viewportSize.x; this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y; 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. * Draws the given tile.
* @param {OpenSeadragon.Tile} tile - The tile to draw. * @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas. * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered}) * 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. * 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(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required'); $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
if ( this.useCanvas ) { if ( this.useCanvas ) {
var context = this._getContext( useSketch );
// TODO do this in a more performant way // TODO do this in a more performant way
// specifically, don't save,rotate,restore every time we draw a tile // specifically, don't save,rotate,restore every time we draw a tile
if( this.viewport.degrees !== 0 ) { if( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.viewport.degrees ); this._offsetForRotation( tile, this.viewport.degrees, useSketch );
tile.drawCanvas( this.context, drawingHandler ); tile.drawCanvas( context, drawingHandler );
this._restoreRotationChanges( tile ); this._restoreRotationChanges( tile, useSketch );
} else { } else {
tile.drawCanvas( this.context, drawingHandler ); tile.drawCanvas( context, drawingHandler );
} }
} else { } else {
tile.drawHTML( this.canvas ); 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 // private
saveContext: function() { saveContext: function( useSketch ) {
if (!this.useCanvas) { if (!this.useCanvas) {
return; 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(); this.context.save();
}, this.context.globalAlpha = opacity;
this.context.drawImage(this.sketchCanvas, 0, 0);
// private
restoreContext: function() {
if (!this.useCanvas) {
return;
}
this.context.restore(); this.context.restore();
}, },
// private // private
setClip: function(rect) { drawDebugInfo: function( tile, count, i ){
if (!this.useCanvas) { if ( !this.useCanvas ) {
return; return;
} }
this.context.beginPath(); var context = this.context;
this.context.rect(rect.x, rect.y, rect.width, rect.height); context.save();
this.context.clip(); context.lineWidth = 2 * $.pixelDensityRatio;
}, context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
context.strokeStyle = this.debugGridColor;
context.fillStyle = this.debugGridColor;
// private if ( this.viewport.degrees !== 0 ) {
drawDebugInfo: function( tile, count, i ){ this._offsetForRotation( tile, this.viewport.degrees );
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();
} }
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 // private
debugRect: function(rect) { debugRect: function(rect) {
if ( this.useCanvas ) { if ( this.useCanvas ) {
this.context.save(); var context = this.context;
this.context.lineWidth = 2 * $.pixelDensityRatio; context.save();
this.context.strokeStyle = this.debugGridColor; context.lineWidth = 2 * $.pixelDensityRatio;
this.context.fillStyle = this.debugGridColor; context.strokeStyle = this.debugGridColor;
context.fillStyle = this.debugGridColor;
this.context.strokeRect( context.strokeRect(
rect.x * $.pixelDensityRatio, rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio, rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio, rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio rect.height * $.pixelDensityRatio
); );
this.context.restore(); context.restore();
} }
}, },
// private // private
_offsetForRotation: function( tile, degrees ){ _offsetForRotation: function( tile, degrees, useSketch ){
var cx = this.canvas.width / 2, var cx = this.canvas.width / 2,
cy = this.canvas.height / 2, cy = this.canvas.height / 2;
px = tile.position.x - cx,
py = tile.position.y - cy;
this.context.save(); var context = this._getContext( useSketch );
context.save();
this.context.translate(cx, cy); context.translate(cx, cy);
this.context.rotate( Math.PI / 180 * degrees); context.rotate( Math.PI / 180 * degrees);
tile.position.x = px; context.translate(-cx, -cy);
tile.position.y = py;
}, },
// private // private
_restoreRotationChanges: function( tile ){ _restoreRotationChanges: function( tile, useSketch ){
var cx = this.canvas.width / 2, var context = this._getContext( useSketch );
cy = this.canvas.height / 2, context.restore();
px = tile.position.x + cx,
py = tile.position.y + cy;
tile.position.x = px;
tile.position.y = py;
this.context.restore();
}, },
// private // private

View File

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

View File

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

View File

@ -133,7 +133,7 @@
*/ */
this.element = $.getElement( options.element ); 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. * will be treated as a click gesture.
* @member {Number} clickTimeThreshold * @member {Number} clickTimeThreshold
* @memberof OpenSeadragon.MouseTracker# * @memberof OpenSeadragon.MouseTracker#
@ -244,7 +244,7 @@
// Active pointers lists. Array of GesturePointList objects, one for each pointer device type. // 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()). // 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. // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
activePointersLists: [], activePointersLists: [],
@ -1032,7 +1032,7 @@
$.MouseTracker.mousePointerId = "legacy-mouse"; $.MouseTracker.mousePointerId = "legacy-mouse";
$.MouseTracker.maxTouchPoints = 10; $.MouseTracker.maxTouchPoints = 10;
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Classes and typedefs // Classes and typedefs
@ -1078,7 +1078,7 @@
/** /**
* @class GesturePointList * @class GesturePointList
* @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type. * @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. * of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
* @memberof OpenSeadragon.MouseTracker * @memberof OpenSeadragon.MouseTracker
* @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
@ -1198,7 +1198,7 @@
return null; return null;
} }
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Utility functions // Utility functions
@ -1282,7 +1282,7 @@
false false
); );
} }
clearTrackedPointers( tracker ); clearTrackedPointers( tracker );
delegate.tracking = true; 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()). * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
* *
* @private * @private
@ -1943,7 +1943,7 @@
handleMouseMove( tracker, event ); handleMouseMove( tracker, event );
} }
/** /**
* This handler is attached to the window object (on the capture phase) to emulate mouse capture. * 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. * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
@ -2191,7 +2191,7 @@
var i, var i,
touchCount = event.changedTouches.length, touchCount = event.changedTouches.length,
gPoints = []; gPoints = [];
for ( i = 0; i < touchCount; i++ ) { for ( i = 0; i < touchCount; i++ ) {
gPoints.push( { gPoints.push( {
id: event.changedTouches[ i ].identifier, id: event.changedTouches[ i ].identifier,
@ -2420,7 +2420,7 @@
*/ */
function startTrackingPointer( pointsList, gPoint ) { 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 // true if the first pointer in the gesture, otherwise false
if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) { if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
if ( pointsList.getLength() === 0 ) { if ( pointsList.getLength() === 0 ) {
@ -2617,7 +2617,7 @@
* Gesture points associated with the event. * Gesture points associated with the event.
* @param {Number} buttonChanged * @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. * 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. * 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. * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false.
@ -2779,7 +2779,7 @@
* Gesture points associated with the event. * Gesture points associated with the event.
* @param {Number} buttonChanged * @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. * 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. * 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. * @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, immediateRender: true,
blendTime: 0, blendTime: 0,
animationTime: 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; options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;

View File

@ -204,7 +204,12 @@
* If 0, adjusts to fit viewer. * If 0, adjusts to fit viewer.
* *
* @property {Number} [opacity=1] * @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] * @property {Number} [degrees=0]
* Initial rotation. * Initial rotation.
@ -236,7 +241,7 @@
* @property {Number} [minZoomImageRatio=0.9] * @property {Number} [minZoomImageRatio=0.9]
* The minimum percentage ( expressed as a number between 0 and 1 ) of * 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. * 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] * @property {Number} [maxZoomPixelRatio=1.1]
* The maximum ratio to allow a zoom-in to affect the highest level pixel * The maximum ratio to allow a zoom-in to affect the highest level pixel
@ -247,6 +252,9 @@
* @property {Boolean} [autoResize=true] * @property {Boolean} [autoResize=true]
* Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior. * 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] * @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line. * 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. * Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.
* *
* @property {Number} [imageLoaderLimit=0] * @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 * it is set to 0 allowing the browser to make the maximum number of
* image requests in parallel as allowed by the browsers policy. * image requests in parallel as allowed by the browsers policy.
* *
@ -348,7 +356,7 @@
* @property {Boolean} [showNavigator=false] * @property {Boolean} [showNavigator=false]
* Set to true to make the navigator minimap appear. * 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. * 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 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. * 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 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. * 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'] * @property {String} [collectionLayout='horizontal']
* If collectionMode is true, specifies whether to arrange vertically or horizontally. * 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 maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
pixelsPerWheelLine: 40, pixelsPerWheelLine: 40,
autoResize: true, autoResize: true,
preserveImageSizeOnResize: false, // requires autoResize=true
//DEFAULT CONTROL SETTINGS //DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE showSequenceControl: true, //SEQUENCE
@ -1013,10 +1026,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
navigatorRotate: true, navigatorRotate: true,
// INITIAL ROTATION // INITIAL ROTATION
degrees: 0, degrees: 0,
// APPEARANCE // APPEARANCE
opacity: 1, opacity: 1,
placeholderFillStyle: null,
//REFERENCE STRIP SETTINGS //REFERENCE STRIP SETTINGS
showReferenceStrip: false, showReferenceStrip: false,
@ -1029,6 +1043,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
//COLLECTION VISUALIZATION SETTINGS //COLLECTION VISUALIZATION SETTINGS
collectionRows: 3, //or columns depending on layout collectionRows: 3, //or columns depending on layout
collectionColumns: 0, //columns in horizontal layout, rows in vertical layout
collectionLayout: 'horizontal', //vertical collectionLayout: 'horizontal', //vertical
collectionMode: false, collectionMode: false,
collectionTileSize: 800, collectionTileSize: 800,
@ -2030,8 +2045,40 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
request.onreadystatechange = function(){}; request.onreadystatechange = function(){};
if ( $.isFunction( onError ) ) { if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest
onError( request, e ); 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 ); 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 * Reports whether the image format is supported for tiling in this

View File

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

View File

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

View File

@ -63,10 +63,23 @@ ImageRecord.prototype = {
}, },
getRenderedContext: function() { 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; return this._renderedContext;
}, },
setRenderedContext: function(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; 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. * may temporarily surpass that number, but should eventually come back down to the max specified.
* @param {Object} options - Tile info. * @param {Object} options - Tile info.
* @param {OpenSeadragon.Tile} options.tile - The tile to cache. * @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 {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 * @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 * 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, "[TileCache.cacheTile] options is required" );
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile 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.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" ); $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0; var cutoff = options.cutoff || 0;
@ -143,8 +156,9 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
var imageRecord = this._imagesLoaded[options.tile.url]; var imageRecord = this._imagesLoaded[options.tile.url];
if (!imageRecord) { if (!imageRecord) {
$.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({ imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
image: options.tile.image image: options.image
}); });
this._imagesLoadedCount++; this._imagesLoadedCount++;
@ -158,6 +172,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
if ( this._imagesLoadedCount > this._maxImageCacheCount ) { if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
var worstTile = null; var worstTile = null;
var worstTileIndex = -1; var worstTileIndex = -1;
var worstTileRecord = null;
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord; var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) { for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
@ -169,6 +184,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
} else if ( !worstTile ) { } else if ( !worstTile ) {
worstTile = prevTile; worstTile = prevTile;
worstTileIndex = i; worstTileIndex = i;
worstTileRecord = prevTileRecord;
continue; continue;
} }
@ -181,11 +197,12 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
( prevTime == worstTime && prevLevel > worstLevel ) ) { ( prevTime == worstTime && prevLevel > worstLevel ) ) {
worstTile = prevTile; worstTile = prevTile;
worstTileIndex = i; worstTileIndex = i;
worstTileRecord = prevTileRecord;
} }
} }
if ( worstTile && worstTileIndex >= 0 ) { if ( worstTile && worstTileIndex >= 0 ) {
this._unloadTile(worstTile); this._unloadTile(worstTileRecord);
insertionIndex = worstTileIndex; insertionIndex = worstTileIndex;
} }
} }
@ -206,7 +223,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
for ( var i = 0; i < this._tilesLoaded.length; ++i ) { for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
tileRecord = this._tilesLoaded[ i ]; tileRecord = this._tilesLoaded[ i ];
if ( tileRecord.tiledImage === tiledImage ) { if ( tileRecord.tiledImage === tiledImage ) {
this._unloadTile(tileRecord.tile); this._unloadTile(tileRecord);
this._tilesLoaded.splice( i, 1 ); this._tilesLoaded.splice( i, 1 );
i--; i--;
} }
@ -220,8 +237,11 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
}, },
// private // private
_unloadTile: function(tile) { _unloadTile: function(tileRecord) {
$.console.assert(tile, '[TileCache._unloadTile] tile is required'); $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');
var tile = tileRecord.tile;
var tiledImage = tileRecord.tiledImage;
tile.unload(); tile.unload();
tile.cacheImageRecord = null; tile.cacheImageRecord = null;
@ -232,6 +252,20 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
delete this._imagesLoaded[tile.url]; delete this._imagesLoaded[tile.url];
this._imagesLoadedCount--; 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 {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}. * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.minPixelRatio] - 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 {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}. * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
*/ */
$.TiledImage = function( options ) { $.TiledImage = function( options ) {
@ -126,21 +128,23 @@ $.TiledImage = function( options ) {
coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
lastDrawn: [], // An unordered list of Tiles drawn last frame. lastDrawn: [], // An unordered list of Tiles drawn last frame.
lastResetTime: 0, // Last time for which the tiledImage was reset. lastResetTime: 0, // Last time for which the tiledImage was reset.
_midDraw: false, // Is the tiledImage currently updating the viewport? _midDraw: false, // Is the tiledImage currently updating the viewport?
_needsDraw: true, // Does the tiledImage need to update the viewport again? _needsDraw: true, // Does the tiledImage need to update the viewport again?
//configurable settings //configurable settings
springStiffness: $.DEFAULT_SETTINGS.springStiffness, springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime, animationTime: $.DEFAULT_SETTINGS.animationTime,
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
immediateRender: $.DEFAULT_SETTINGS.immediateRender, immediateRender: $.DEFAULT_SETTINGS.immediateRender,
blendTime: $.DEFAULT_SETTINGS.blendTime, blendTime: $.DEFAULT_SETTINGS.blendTime,
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
debugMode: $.DEFAULT_SETTINGS.debugMode, debugMode: $.DEFAULT_SETTINGS.debugMode,
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity
}, options ); }, 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. * Sets the TiledImage's position in the world.
* @param {OpenSeadragon.Point} position - The new position, in viewport coordinates. * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.
@ -484,6 +565,21 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._needsDraw = true; 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 // private
_setScale: function(scale, immediately) { _setScale: function(scale, immediately) {
var sameTarget = (this._scaleSpring.target.value === scale); var sameTarget = (this._scaleSpring.target.value === scale);
@ -696,8 +792,6 @@ function updateViewport( tiledImage ) {
// Load the new 'best' tile // Load the new 'best' tile
if ( best ) { if ( best ) {
loadTile( tiledImage, best, currentTime ); 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) { if (!tile.loaded) {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url); var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
if (imageRecord) { if (imageRecord) {
tile.loaded = true; var image = imageRecord.getImage();
tile.image = imageRecord.getImage(); setTileLoaded(tiledImage, tile, image);
tiledImage._tileCache.cacheTile({
tile: tile,
tiledImage: tiledImage
});
} }
} }
@ -922,8 +1011,8 @@ function loadTile( tiledImage, tile, time ) {
tiledImage._imageLoader.addJob({ tiledImage._imageLoader.addJob({
src: tile.url, src: tile.url,
crossOriginPolicy: tiledImage.crossOriginPolicy, crossOriginPolicy: tiledImage.crossOriginPolicy,
callback: function( image ){ callback: function( image, errorMsg ){
onTileLoad( tiledImage, tile, time, image ); onTileLoad( tiledImage, tile, time, image, errorMsg );
}, },
abort: function() { abort: function() {
tile.loading = false; 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 ) { 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 ){ if( !tiledImage.debugMode ){
tile.loading = false; tile.loading = false;
tile.exists = false; tile.exists = false;
@ -946,16 +1035,9 @@ function onTileLoad( tiledImage, tile, time, image ) {
} }
var finish = function() { var finish = function() {
tile.loading = false; var cutoff = Math.ceil( Math.log(
tile.loaded = true; tiledImage.source.getTileWidth(tile.level) ) / Math.log( 2 ) );
tile.image = image; setTileLoaded(tiledImage, tile, image, cutoff);
var cutoff = Math.ceil( Math.log( tiledImage.source.getTileSize(tile.level) ) / Math.log( 2 ) );
tiledImage._tileCache.cacheTile({
tile: tile,
cutoff: cutoff,
tiledImage: tiledImage
});
}; };
// Check if we're mid-update; this can happen on IE8 because image load events for // 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 // Wait until after the update, in case caching unloads any tiles
window.setTimeout( finish, 1); 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 ){ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
var boundsTL = tile.bounds.getTopLeft(); var boundsTL = tile.bounds.getTopLeft();
@ -1148,42 +1275,49 @@ function compareTiles( previousBest, tile ) {
return previousBest; return previousBest;
} }
function drawTiles( tiledImage, lastDrawn ){ function drawTiles( tiledImage, lastDrawn ) {
var i, var i,
tile, tile;
tileKey,
viewer, if ( tiledImage.opacity <= 0 ) {
viewport, drawDebugInfo( tiledImage, lastDrawn );
position, return;
tileSource; }
var useSketch = tiledImage.opacity < 1;
if ( useSketch ) {
tiledImage._drawer._clear( true );
}
var usedClip = false; var usedClip = false;
if (tiledImage._clip) { if ( tiledImage._clip ) {
tiledImage._drawer.saveContext(); tiledImage._drawer.saveContext(useSketch);
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true); var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
var topLeft = tiledImage.viewport.pixelFromPoint(box.getTopLeft(), true); var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
var size = tiledImage.viewport.deltaPixelsFromPoints(box.getSize(), true); tiledImage._drawer.setClip(clipRect, useSketch);
box = new OpenSeadragon.Rect(topLeft.x * $.pixelDensityRatio,
topLeft.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio);
tiledImage._drawer.setClip(box);
usedClip = true; 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-- ) { for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
tile = lastDrawn[ i ]; tile = lastDrawn[ i ];
tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler ); tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch );
tile.beingDrawn = true; tile.beingDrawn = true;
if( tiledImage.debugMode ){
try{
tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i );
}catch(e){
$.console.error(e);
}
}
if( tiledImage.viewer ){ if( tiledImage.viewer ){
/** /**
* <em>- Needs documentation -</em> * <em>- Needs documentation -</em>
@ -1203,8 +1337,26 @@ function drawTiles( tiledImage, lastDrawn ){
} }
} }
if (usedClip) { if ( usedClip ) {
tiledImage._drawer.restoreContext(); 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. * 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 * Tile size determines the point at which the image pyramid must be
* divided into a matrix of smaller images. * 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] * @param {Number} [options.tileOverlap]
* The number of pixels each tile is expected to overlap touching tiles. * The number of pixels each tile is expected to overlap touching tiles.
* @param {Number} [options.minLevel] * @param {Number} [options.minLevel]
@ -137,13 +142,6 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
* @member {OpenSeadragon.Point} dimensions * @member {OpenSeadragon.Point} dimensions
* @memberof OpenSeadragon.TileSource# * @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. * The overlap in pixels each tile shares with its adjacent neighbors.
* @member {Number} tileOverlap * @member {Number} tileOverlap
@ -174,7 +172,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//async mechanism set some safe defaults first //async mechanism set some safe defaults first
this.aspectRatio = 1; this.aspectRatio = 1;
this.dimensions = new $.Point( 10, 10 ); this.dimensions = new $.Point( 10, 10 );
this.tileSize = 0; this._tileWidth = 0;
this._tileHeight = 0;
this.tileOverlap = 0; this.tileOverlap = 0;
this.minLevel = 0; this.minLevel = 0;
this.maxLevel = 0; this.maxLevel = 0;
@ -191,7 +190,29 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.aspectRatio = ( options.width && options.height ) ? this.aspectRatio = ( options.width && options.height ) ?
( options.width / options.height ) : 1; ( options.width / options.height ) : 1;
this.dimensions = new $.Point( options.width, options.height ); 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.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
this.minLevel = options.minLevel ? options.minLevel : 0; this.minLevel = options.minLevel ? options.minLevel : 0;
this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ? 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 */{ $.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. * Return the tileWidth for a given level.
* Subclasses should override this if tileSizes can be different at different levels * Subclasses should override this if tileWidth can be different at different levels
* such as in IIIFTileSource. Code should use this function rather than reading * 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 * @function
* @param {Number} level * @param {Number} level
*/ */
getTileSize: function( level ) { getTileWidth: function( level ) {
return this.tileSize; 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 ) { getNumTiles: function( level ) {
var scale = this.getLevelScale( level ), var scale = this.getLevelScale( level ),
x = Math.ceil( scale * this.dimensions.x / this.getTileSize(level) ), x = Math.ceil( scale * this.dimensions.x / this.getTileWidth(level) ),
y = Math.ceil( scale * this.dimensions.y / this.getTileSize(level) ); y = Math.ceil( scale * this.dimensions.y / this.getTileHeight(level) );
return new $.Point( x, y ); return new $.Point( x, y );
}, },
@ -277,10 +324,15 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
var i, var i,
tilesPerSide, tilesPerSide,
tiles; tiles;
for( i = this.minLevel; i < this.maxLevel; i++ ){ for( i = this.minLevel; i < this.maxLevel; i++ ){
tiles = this.getNumTiles( i ); tiles = this.getNumTiles( i );
tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.getTileSize(i) ); tilesPerSide = new $.Point(
if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){ 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; break;
} }
} }
@ -293,9 +345,9 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
* @param {OpenSeadragon.Point} point * @param {OpenSeadragon.Point} point
*/ */
getTileAtPoint: function( level, point ) { getTileAtPoint: function( level, point ) {
var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ), var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level) ),
tx = Math.floor( pixel.x / this.getTileSize(level) ), tx = Math.floor( pixel.x / this.getTileWidth(level) ),
ty = Math.floor( pixel.y / this.getTileSize(level) ); ty = Math.floor( pixel.y / this.getTileHeight(level) );
return new $.Point( tx, ty ); return new $.Point( tx, ty );
}, },
@ -308,11 +360,12 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
*/ */
getTileBounds: function( level, x, y ) { getTileBounds: function( level, x, y ) {
var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ), var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
tileSize = this.getTileSize(level), tileWidth = this.getTileWidth(level),
px = ( x === 0 ) ? 0 : tileSize * x - this.tileOverlap, tileHeight = this.getTileHeight(level),
py = ( y === 0 ) ? 0 : tileSize * y - this.tileOverlap, px = ( x === 0 ) ? 0 : tileWidth * x - this.tileOverlap,
sx = tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap, py = ( y === 0 ) ? 0 : tileHeight * y - this.tileOverlap,
sy = tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap, sx = tileWidth + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
sy = tileHeight + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
scale = 1.0 / dimensionsScaled.x; scale = 1.0 / dimensionsScaled.x;
sx = Math.min( sx, dimensionsScaled.x - px ); sx = Math.min( sx, dimensionsScaled.x - px );
@ -560,8 +613,7 @@ function processResponse( xhr ){
data = xhr.responseText; data = xhr.responseText;
} }
}else if( responseText.match(/\s*[\{\[].*/) ){ }else if( responseText.match(/\s*[\{\[].*/) ){
/*jshint evil:true*/ data = $.parseJSON(responseText);
data = eval( '('+responseText+')' );
}else{ }else{
data = responseText; data = responseText;
} }

View File

@ -374,7 +374,6 @@ $.Viewer = function( options ) {
viewer: this, viewer: this,
viewport: this.viewport, viewport: this.viewport,
element: this.canvas, element: this.canvas,
opacity: this.opacity,
debugGridColor: this.debugGridColor 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}). * 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 * @event pre-full-screen
* @memberof OpenSeadragon.Viewer * @memberof OpenSeadragon.Viewer
@ -1191,6 +1193,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* named 'getTileUrl', it is treated as a custom TileSource. * named 'getTileUrl', it is treated as a custom TileSource.
* @param {Number} [options.index] The index of the item. Added on top of * @param {Number} [options.index] The index of the item. Added on top of
* all other items if not specified. * 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.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.y=0] The Y position for the image in viewport coordinates.
* @param {Number} [options.width=1] The width 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 * @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 * (portions of the image outside of this area will not be visible). Only works on
* browsers that support the HTML5 canvas. * 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 * @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: * successfully added. It's passed the event object which contains a single property:
* "item", the resulting TiledImage. * "item", the resulting TiledImage.
@ -1206,17 +1213,31 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* and "source" properties. * and "source" properties.
* @param {Boolean} [options.collectionImmediately=false] If collectionMode is on, * @param {Boolean} [options.collectionImmediately=false] If collectionMode is on,
* specifies whether to snap to the new arrangement immediately or to animate to it. * 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.World.event:add-item
* @fires OpenSeadragon.Viewer.event:add-item-failed * @fires OpenSeadragon.Viewer.event:add-item-failed
*/ */
addTiledImage: function( options ) { addTiledImage: function( options ) {
$.console.assert(options, "[Viewer.addTiledImage] options is required"); $.console.assert(options, "[Viewer.addTiledImage] options is required");
$.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource 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; var _this = this;
if (options.replace) {
options.replaceItem = _this.world.getItemAt(options.index);
}
this._hideMessage(); this._hideMessage();
if (options.placeholderFillStyle === undefined) {
options.placeholderFillStyle = this.placeholderFillStyle;
}
if (options.opacity === undefined) {
options.opacity = this.opacity;
}
var myQueueItem = { var myQueueItem = {
options: options options: options
}; };
@ -1272,6 +1293,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this._loadQueue.splice(0, 1); _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({ tiledImage = new $.TiledImage({
viewer: _this, viewer: _this,
source: queueItem.tileSource, source: queueItem.tileSource,
@ -1284,6 +1313,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
width: queueItem.options.width, width: queueItem.options.width,
height: queueItem.options.height, height: queueItem.options.height,
clip: queueItem.options.clip, clip: queueItem.options.clip,
placeholderFillStyle: queueItem.options.placeholderFillStyle,
opacity: queueItem.options.opacity,
springStiffness: _this.springStiffness, springStiffness: _this.springStiffness,
animationTime: _this.animationTime, animationTime: _this.animationTime,
minZoomImageRatio: _this.minZoomImageRatio, minZoomImageRatio: _this.minZoomImageRatio,
@ -1305,6 +1336,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this.world.arrange({ _this.world.arrange({
immediately: queueItem.options.collectionImmediately, immediately: queueItem.options.collectionImmediately,
rows: _this.collectionRows, rows: _this.collectionRows,
columns: _this.collectionColumns,
layout: _this.collectionLayout, layout: _this.collectionLayout,
tileSize: _this.collectionTileSize, tileSize: _this.collectionTileSize,
tileMargin: _this.collectionTileMargin tileMargin: _this.collectionTileMargin
@ -1697,7 +1729,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* is closed which include when changing page. * is closed which include when changing page.
* @method * @method
* @param {Element|String|Object} element - A reference to an element or an id for * @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 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed. * rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the * @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 * Updates the overlay represented by the reference to the element or
* element id moving it to the new location, relative to the new placement. * element id moving it to the new location, relative to the new placement.
* @method * @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 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed. * rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
@ -1980,8 +2014,7 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
if ( tileSource.match( /\s*<.*/ ) ) { if ( tileSource.match( /\s*<.*/ ) ) {
tileSource = $.parseXml( tileSource ); tileSource = $.parseXml( tileSource );
} else if ( tileSource.match( /\s*[\{\[].*/ ) ) { } else if ( tileSource.match( /\s*[\{\[].*/ ) ) {
/*jshint evil:true*/ tileSource = $.parseJSON(tileSource);
tileSource = eval( '(' + tileSource + ')' );
} }
} }
@ -2206,7 +2239,7 @@ function onCanvasKeyDown( event ) {
if ( event.shift ) { if ( event.shift ) {
this.viewport.zoomBy(1.1); this.viewport.zoomBy(1.1);
} else { } else {
this.viewport.panBy(new $.Point(0, -0.05)); this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
} }
this.viewport.applyConstraints(); this.viewport.applyConstraints();
return false; return false;
@ -2214,16 +2247,16 @@ function onCanvasKeyDown( event ) {
if ( event.shift ) { if ( event.shift ) {
this.viewport.zoomBy(0.9); this.viewport.zoomBy(0.9);
} else { } else {
this.viewport.panBy(new $.Point(0, 0.05)); this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
} }
this.viewport.applyConstraints(); this.viewport.applyConstraints();
return false; return false;
case 37://left arrow 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(); this.viewport.applyConstraints();
return false; return false;
case 39://right arrow 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(); this.viewport.applyConstraints();
return false; return false;
default: default:
@ -2255,7 +2288,7 @@ function onCanvasKeyPress( event ) {
if ( event.shift ) { if ( event.shift ) {
this.viewport.zoomBy(1.1); this.viewport.zoomBy(1.1);
} else { } else {
this.viewport.panBy(new $.Point(0, -0.05)); this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
} }
this.viewport.applyConstraints(); this.viewport.applyConstraints();
return false; return false;
@ -2264,16 +2297,16 @@ function onCanvasKeyPress( event ) {
if ( event.shift ) { if ( event.shift ) {
this.viewport.zoomBy(0.9); this.viewport.zoomBy(0.9);
} else { } else {
this.viewport.panBy(new $.Point(0, 0.05)); this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
} }
this.viewport.applyConstraints(); this.viewport.applyConstraints();
return false; return false;
case 97://a case 97://a
this.viewport.panBy(new $.Point(-0.05, 0)); this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
this.viewport.applyConstraints(); this.viewport.applyConstraints();
return false; return false;
case 100://d case 100://d
this.viewport.panBy(new $.Point(0.05, 0)); this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints(); this.viewport.applyConstraints();
return false; return false;
default: default:
@ -2811,13 +2844,31 @@ function updateOnce( viewer ) {
return; return;
} }
var containerSize;
if ( viewer.autoResize ) { if ( viewer.autoResize ) {
var containerSize = _getSafeElemSize( viewer.container ); containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) { if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
// maintain image position if ( viewer.preserveImageSizeOnResize ) {
var oldBounds = viewer.viewport.getBounds(); var prevContainerSize = THIS[ viewer.hash ].prevContainerSize;
var oldCenter = viewer.viewport.getCenter(); var bounds = viewer.viewport.getBounds(true);
resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter); 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 ].prevContainerSize = containerSize;
THIS[ viewer.hash ].forceRedraw = true; THIS[ viewer.hash ].forceRedraw = true;
} }
@ -2914,19 +2965,15 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter
viewport.resize( containerSize, true ); 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( var newBounds = new $.Rect(
oldCenter.x - ( newWidth / 2.0 ), oldCenter.x - ( oldBounds.width / 2.0 ),
oldCenter.y - ( newHeight / 2.0 ), oldCenter.y - ( oldBounds.height / 2.0 ),
newWidth, oldBounds.width,
newHeight oldBounds.height
); );
viewport.fitBounds( newBounds, true );
// let the viewport decide if the bounds are too big or too small
viewport.fitBoundsWithConstraints( newBounds, true );
} }
function drawWorld( viewer ) { 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 {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.
* @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}. * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.
* @param {Number} [options.rows] - See collectionRows 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.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}. * @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.
* @fires OpenSeadragon.World.event:metrics-change * @fires OpenSeadragon.World.event:metrics-change
@ -290,10 +291,16 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
var immediately = options.immediately || false; var immediately = options.immediately || false;
var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout; var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;
var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows; var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;
var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;
var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize; var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;
var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin; var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;
var increment = tileSize + tileMargin; 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 x = 0;
var y = 0; var y = 0;
var item, box, width, height, position; var item, box, width, height, position;

View File

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

View File

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

View File

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

View File

@ -23,19 +23,19 @@
<script type="text/javascript"> <script type="text/javascript">
var _viewer; var _viewer;
var generateUniqueHash = (function() { var generateUniqueHash = (function() {
var counter = 0; var counter = 0;
return function() { return function() {
return "openseadragon_" + (counter++); return "openseadragon_" + (counter++);
}; };
})(); })();
function createViewer() { function createViewer() {
if ( _viewer ) { if ( _viewer ) {
destroyViewer(); destroyViewer();
} }
_viewer = OpenSeadragon({ _viewer = OpenSeadragon({
element: document.getElementById("contentDiv"), element: document.getElementById("contentDiv"),
showNavigationControl: false, showNavigationControl: false,
@ -44,7 +44,7 @@
tileSources: "../data/testpattern.dzi" tileSources: "../data/testpattern.dzi"
}); });
} }
function destroyViewer() { function destroyViewer() {
if ( _viewer ) { if ( _viewer ) {
_viewer.destroy(); _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(); createViewer();
ok(viewer.drawer, 'Drawer exists'); ok(viewer.drawer, 'Drawer exists');
equal(viewer.drawer.canRotate(), OpenSeadragon.supportsCanvas, 'we can rotate if we have canvas'); 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(); 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() { asyncTest('deprecations', function() {
createViewer(); createViewer({
Util.testDeprecation(viewer.drawer, 'addOverlay', viewer, 'addOverlay'); tileSources: '/test/data/testpattern.dzi'
Util.testDeprecation(viewer.drawer, 'updateOverlay', viewer, 'updateOverlay'); });
Util.testDeprecation(viewer.drawer, 'removeOverlay', viewer, 'removeOverlay'); viewer.world.addHandler('add-item', function() {
Util.testDeprecation(viewer.drawer, 'clearOverlays', viewer, 'clearOverlays'); Util.testDeprecation(viewer.drawer, 'addOverlay', viewer, 'addOverlay');
Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw'); Util.testDeprecation(viewer.drawer, 'updateOverlay', viewer, 'updateOverlay');
Util.testDeprecation(viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded'); Util.testDeprecation(viewer.drawer, 'removeOverlay', viewer, 'removeOverlay');
Util.testDeprecation(viewer.drawer, 'reset', viewer.world, 'resetItems'); Util.testDeprecation(viewer.drawer, 'clearOverlays', viewer, 'clearOverlays');
Util.testDeprecation(viewer.drawer, 'update', viewer.world, 'draw'); Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw');
start(); 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' ); 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 viewer = null;
// ---------- // ----------
var testOpen = function(name) { var testOpenUrl = function(relativeUrl) {
testOpen('/test/data/' + relativeUrl);
};
var testOpen = function(tileSource) {
$(document).ready(function() { $(document).ready(function() {
var timeWatcher = Util.timeWatcher(7000); var timeWatcher = Util.timeWatcher(7000);
viewer = OpenSeadragon({ viewer = OpenSeadragon({
id: 'example', id: 'example',
prefixUrl: '/build/openseadragon/images/', prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/' + name tileSources: tileSource
}); });
ok(viewer, 'Viewer exists'); ok(viewer, 'Viewer exists');
@ -52,62 +56,90 @@
// ---------- // ----------
asyncTest('DZI', function() { asyncTest('DZI', function() {
testOpen('testpattern.dzi'); testOpenUrl('testpattern.dzi');
}); });
// ---------- // ----------
asyncTest('DZI JSONp', function() { asyncTest('DZI JSONp', function() {
testOpen('testpattern.js'); testOpenUrl('testpattern.js');
}); });
// ---------- // ----------
asyncTest('DZI XML', function() { asyncTest('DZI XML', function() {
testOpen('testpattern.xml'); testOpenUrl('testpattern.xml');
}); });
// ---------- // ----------
asyncTest('DZI XML with query parameter', function() { asyncTest('DZI XML with query parameter', function() {
testOpen('testpattern.xml?param=value'); testOpenUrl('testpattern.xml?param=value');
}); });
// ---------- // ----------
asyncTest('IIIF 1.0 JSON', function() { 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() { 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() { 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() { 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() { 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() { 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() { 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() { 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() { asyncTest( 'Multi-image operations', function() {
expect( 21 ); expect( 24 );
viewer.addHandler( "open", function( ) { viewer.addHandler( "open", function( ) {
equal( 1, viewer.world.getItemCount( ), equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." ); "One item should be present after opening." );
@ -95,22 +95,36 @@
equal( viewer.world.getIndexOfItem( item2 ), 1, equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 2 should stay at index 1." ); "Item 2 should stay at index 1." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) { options.index = 2;
viewer.world.removeHandler( "remove-item", removeItemHandler ); 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, equal( item2, event.item, "Removed item should be item2." );
"Item 1 should be at index 2." );
equal( viewer.world.getIndexOfItem( item2 ), -1,
"Item 2 should be at index -1." );
equal( viewer.world.getIndexOfItem( item3 ), 1,
"Item 3 should be at index 1." );
start(); 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() { asyncTest('basics', function() {
var fakeTiledImage0 = {}; var fakeViewer = {
var fakeTiledImage1 = {}; raiseEvent: function() {}
};
var fakeTiledImage0 = {
viewer: fakeViewer
};
var fakeTiledImage1 = {
viewer: fakeViewer
};
var fakeTile0 = { var fakeTile0 = {
url: 'foo.jpg', url: 'foo.jpg',
@ -58,7 +65,12 @@
// ---------- // ----------
asyncTest('maxImageCacheCount', function() { asyncTest('maxImageCacheCount', function() {
var fakeTiledImage0 = {}; var fakeViewer = {
raiseEvent: function() {}
};
var fakeTiledImage0 = {
viewer: fakeViewer
};
var fakeTile0 = { var fakeTile0 = {
url: 'different.jpg', 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 */ /* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() { (function () {
var viewer; var viewer;
module('Units', { module('Units', {
@ -10,8 +10,8 @@
testLog.reset(); testLog.reset();
viewer = OpenSeadragon({ viewer = OpenSeadragon({
id: 'unitsexample', id: 'unitsexample',
prefixUrl: '/build/openseadragon/images/', prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests springStiffness: 100 // Faster animation = faster tests
}); });
}, },
@ -30,38 +30,57 @@
Util.assessNumericValue(a.y, b.y, 0.00000001, message); Util.assessNumericValue(a.y, b.y, 0.00000001, message);
} }
// ---------- // Check that f^-1 ( f(x) ) = x
asyncTest('Coordinates conversions', function() { function checkPoint(context) {
var viewport = viewer.viewport;
function checkPoint(context) { var point = new OpenSeadragon.Point(15, 12);
var viewport = viewer.viewport; var result = viewport.viewerElementToImageCoordinates(
var point = new OpenSeadragon.Point(15, 12);
var result = viewport.viewerElementToImageCoordinates(
viewport.imageToViewerElementCoordinates(point)); 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)); 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)); 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)); 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 () { viewer.addHandler("open", function () {
var viewport = viewer.viewport; var viewport = viewer.viewport;
var tiledImage = viewer.world.getItemAt(0);
var point0_0 = new OpenSeadragon.Point(0, 0); var point0_0 = new OpenSeadragon.Point(0, 0);
var point = viewport.viewerElementToViewportCoordinates(point0_0); var point = viewport.viewerElementToViewportCoordinates(point0_0);
pointEqual(point, point0_0, 'When opening, viewer coordinate 0,0 is also point 0,0'); pointEqual(point, point0_0, 'When opening, viewer coordinate 0,0 is also point 0,0');
var pixel = viewport.viewerElementToImageCoordinates(point0_0); var viewportPixel = viewport.viewerElementToImageCoordinates(point0_0);
pointEqual(pixel, point0_0, 'When opening, viewer coordinate 0,0 is also pixel 0,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 viewerWidth = $(viewer.element).width();
var imageWidth = viewer.source.dimensions.x; var imageWidth = viewer.source.dimensions.x;
@ -69,15 +88,17 @@
var viewerTopRight = new OpenSeadragon.Point(viewerWidth, 0); var viewerTopRight = new OpenSeadragon.Point(viewerWidth, 0);
var imageTopRight = new OpenSeadragon.Point(imageWidth, 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.'); pointEqual(point, point1_0, 'Viewer top right has viewport coordinates 1,0.');
var pixel = viewport.viewerElementToImageCoordinates(viewerTopRight); viewportPixel = viewport.viewerElementToImageCoordinates(viewerTopRight);
pointEqual(pixel, imageTopRight, 'Viewer top right has viewport coordinates imageWidth,0.'); 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.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler); viewer.removeHandler('animation-finish', animationHandler);
checkPoint('after zoom and pan'); checkPoint(' after zoom and pan');
start(); start();
}); });
viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2)); viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2));
@ -85,19 +106,80 @@
viewer.open('/test/data/testpattern.dzi'); 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 () { viewer.addHandler("open", function () {
var viewport = viewer.viewport; var viewport = viewer.viewport;
var imageWidth = 1000; var imageWidth = viewer.source.dimensions.x;
function getCurrentImageWidth() { function getCurrentImageWidth() {
return viewport.viewportToViewerElementCoordinates( return viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(1, 0)).minus( new OpenSeadragon.Point(1, 0)).minus(
viewport.viewportToViewerElementCoordinates( viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0, 0))).x; new OpenSeadragon.Point(0, 0))).x;
} }
function checkZoom() { function checkZoom() {
@ -105,16 +187,16 @@
var expectedImageZoom = currentImageWidth / imageWidth; var expectedImageZoom = currentImageWidth / imageWidth;
var expectedViewportZoom = viewport.getZoom(true); var expectedViewportZoom = viewport.getZoom(true);
var actualImageZoom = viewport.viewportToImageZoom( var actualImageZoom = viewport.viewportToImageZoom(
expectedViewportZoom); expectedViewportZoom);
equal(actualImageZoom, expectedImageZoom); equal(actualImageZoom, expectedImageZoom);
var actualViewportZoom = viewport.imageToViewportZoom(actualImageZoom); var actualViewportZoom = viewport.imageToViewportZoom(actualImageZoom);
equal(actualViewportZoom, expectedViewportZoom); equal(actualViewportZoom, expectedViewportZoom);
} }
checkZoom(); checkZoom();
var zoomHandler = function() { var zoomHandler = function () {
viewer.removeHandler('animation-finish', zoomHandler); viewer.removeHandler('animation-finish', zoomHandler);
checkZoom(); checkZoom();
start(); start();
@ -127,5 +209,61 @@
viewer.open('/test/data/testpattern.dzi'); 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'); 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(); start();
}); });

View File

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