diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..61b85e21
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/.jshintrc b/.jshintrc
index 2c3f7d32..e40e7d81 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,14 +1,14 @@
{
- "browser": true,
- "curly": true,
- "eqeqeq": false,
- "loopfunc": false,
- "noarg": true,
- "trailing": true,
- "undef": true,
- "unused": false,
+ "browser": true,
+ "curly": true,
+ "eqeqeq": false,
+ "loopfunc": false,
+ "noarg": true,
+ "trailing": true,
+ "undef": true,
+ "unused": false,
- "globals": {
- "OpenSeadragon": true
- }
+ "globals": {
+ "OpenSeadragon": true
+ }
}
diff --git a/README.md b/README.md
index 75fc5c92..efb8b4be 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ The report shows up at `coverage/html/index.html` viewable in a browser.
OpenSeadragon is truly a community project; we welcome your involvement!
-When contributing, please attempt to match the code style already in the codebase. Note that we use four spaces per indentation stop. For more thoughts on code style, see https://github.com/rwldrn/idiomatic.js/.
+When contributing, please attempt to match the code style already in the codebase. Note that we use four spaces per indentation stop. For easier setup you can also install [EditorConfig](http://editorconfig.org/) if your IDE is supported. For more thoughts on code style, see [idiomatic.js](https://github.com/rwldrn/idiomatic.js/).
When fixing bugs and adding features, when appropriate please also:
@@ -86,6 +86,6 @@ If you're new to open source in general, check out [GitHub's open source intro g
## License
-OpenSeadragon is released under the New BSD license. For details, see the file LICENSE.txt.
+OpenSeadragon is released under the New BSD license. For details, see the file LICENSE.txt.
[](http://travis-ci.org/openseadragon/openseadragon)
diff --git a/changelog.txt b/changelog.txt
index 5ff344fb..a76881cb 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,7 +1,28 @@
OPENSEADRAGON CHANGELOG
=======================
-1.3.0: (in progress)
+2.1.0: (in progress)
+* BREAKING CHANGE: the tile does not hold a reference to its image anymore. Only the tile cache keep a reference to images.
+* DEPRECATION: let ImageRecord.getRenderedContext create the rendered context instead of using ImageRecord.setRenderedContext
+* DEPRECATION: TileSource.getTileSize is deprecated. Use TileSource.getTileWidth and TileSource.getTileHeight instead.
+* Added "tile-loaded" event on the viewer allowing to modify a tile before it is marked ready to be drawn (#659)
+* Added "tile-unloaded" event on the viewer allowing to free up memory one has allocated on a tile (#659)
+* Fixed flickering tiles with useCanvas=false when no cache is used (#661)
+* Added additional coordinates conversion methods to TiledImage (#662)
+* 'display: none' no longer gets reset on overlays during draw (#668)
+* Added `preserveImageSizeOnResize` option (#666)
+* Better error reporting for tile load failures (#679)
+* Added collectionColumns as a configuration parameter (#680)
+* Added support for non-square tiles (#673)
+ * TileSource.Options objects can now optionally provide tileWidth/tileHeight instead of tileSize for non-square tile support.
+ * IIIFTileSources will now respect non-square tiles if available.
+* Added XDomainRequest as fallback method for ajax requests if XMLHttpRequest fails (for IE < 10) (#693)
+* Now avoiding using eval when JSON.parse is available (#696)
+* Rotation now works properly on retina display (#708)
+* Added option in addTiledImage to replace tiledImage at index (#706)
+* Changed resize behaviour to prevent "snapping" to world bounds when constraints allow more space (#711)
+
+2.0.0:
* True multi-image mode (#450)
* BREAKING CHANGE: Passing an array for the tileSources option is no longer enough to trigger sequence mode; you have to set the sequenceMode option to true as well
@@ -37,6 +58,8 @@ OPENSEADRAGON CHANGELOG
* Viewport.open supports positioning config properties
* For multi-image open, drawing isn't started until all tileSources have been opened
* You can specify a clip area for each image (only works on browsers that support the HTML5 canvas) (#594)
+ * Added placeholderFillStyle so image rectangles can be drawn even before their tiles load (#635)
+ * Ability to set opacity on individual TiledImages (#644)
* Margins option to push the home region in from the edges of the Viewer (#505)
* Rect and Point toString() functions are now consistent: rounding values to nearest hundredth
* Overlays appear in the DOM immediately on open or addOverlay (#507)
@@ -57,6 +80,8 @@ OPENSEADRAGON CHANGELOG
* Fixed: Cross Origin policy not working (#613)
* Optimized tile loading by clearing the queue on a re-draw when imageLoaderLimit is set (#616)
* Now animating zoom spring exponentially (#631)
+* Added http://editorconfig.org/ config file (#637)
+* Keyboard pan speed is now the same regardless of zoom level (#645)
1.2.1:
diff --git a/package.json b/package.json
index 99e60e8a..d2081b58 100644
--- a/package.json
+++ b/package.json
@@ -1,20 +1,20 @@
{
"name": "OpenSeadragon",
- "version": "1.2.1",
+ "version": "2.0.0",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.5.0",
- "grunt-text-replace": "^0.3.11",
+ "grunt-contrib-compress": "^0.9.1",
"grunt-contrib-concat": "^0.4.0",
- "grunt-git-describe": "^2.3.2",
+ "grunt-contrib-connect": "^0.7.1",
+ "grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1",
- "grunt-contrib-jshint": "^0.10.0",
- "grunt-contrib-compress": "^0.9.1",
- "grunt-contrib-connect": "^0.7.1",
- "qunitjs": "^1.14.0",
- "grunt-qunit-istanbul": "^0.4.5"
+ "grunt-git-describe": "^2.3.2",
+ "grunt-qunit-istanbul": "^0.5.0",
+ "grunt-text-replace": "^0.3.11",
+ "qunitjs": "^1.18.0"
},
"scripts": {
"test": "grunt test"
diff --git a/src/buttongroup.js b/src/buttongroup.js
index 49cc9c2e..77d61371 100644
--- a/src/buttongroup.js
+++ b/src/buttongroup.js
@@ -68,7 +68,7 @@ $.ButtonGroup = function( options ) {
*/
this.element = options.element || $.makeNeutralElement( "div" );
- // TODO What if there IS an options.group specified?
+ // TODO What if there IS an options.group specified?
if( !options.group ){
this.label = $.makeNeutralElement( "label" );
//TODO: support labels for ButtonGroups
diff --git a/src/drawer.js b/src/drawer.js
index 1f18f102..9b74b981 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -42,11 +42,9 @@
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
* @param {Element} options.element - Parent element.
- * @param {Number} [options.opacity=1] - See opacity in {@link OpenSeadragon.Options} for details.
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
*/
$.Drawer = function( options ) {
- var _this = this;
$.console.assert( options.viewer, "[Drawer] options.viewer is required" );
@@ -72,7 +70,9 @@ $.Drawer = function( options ) {
this.viewer = options.viewer;
this.viewport = options.viewport;
this.debugGridColor = options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
- this.opacity = options.opacity === undefined ? $.DEFAULT_SETTINGS.opacity : options.opacity;
+ if (options.opacity) {
+ $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
+ }
this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
/**
@@ -96,6 +96,13 @@ $.Drawer = function( options ) {
*/
this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
+ /**
+ * Sketch canvas used to temporarily draw tiles which cannot be drawn directly
+ * to the main canvas due to opacity. Lazily initialized.
+ */
+ this.sketchCanvas = null;
+ this.sketchContext = null;
+
/**
* @member {Element} element
* @memberof OpenSeadragon.Drawer#
@@ -160,8 +167,11 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* @return {OpenSeadragon.Drawer} Chainable.
*/
setOpacity: function( opacity ) {
- this.opacity = opacity;
- $.setElementOpacity( this.canvas, this.opacity, true );
+ $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
+ var world = this.viewer.world;
+ for (var i = 0; i < world.getItemCount(); i++) {
+ world.getItemAt( i ).setOpacity( opacity );
+ }
return this;
},
@@ -170,7 +180,16 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* @returns {Number}
*/
getOpacity: function() {
- return this.opacity;
+ $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
+ var world = this.viewer.world;
+ var maxOpacity = 0;
+ for (var i = 0; i < world.getItemCount(); i++) {
+ var opacity = world.getItemAt( i ).getOpacity();
+ if ( opacity > maxOpacity ) {
+ maxOpacity = opacity;
+ }
+ }
+ return maxOpacity;
},
// deprecated
@@ -214,6 +233,8 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
//force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
this.canvas.width = 1;
this.canvas.height = 1;
+ this.sketchCanvas = null;
+ this.sketchContext = null;
},
/**
@@ -227,189 +248,260 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
this.canvas.height != viewportSize.y ) {
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
+ if ( this.sketchCanvas !== null ) {
+ this.sketchCanvas.width = this.canvas.width;
+ this.sketchCanvas.height = this.canvas.height;
+ }
}
- this.context.clearRect( 0, 0, viewportSize.x, viewportSize.y );
+ this._clear();
}
},
+ _clear: function ( useSketch ) {
+ if ( !this.useCanvas ) {
+ return;
+ }
+ var context = this._getContext( useSketch );
+ var canvas = context.canvas;
+ context.clearRect( 0, 0, canvas.width, canvas.height );
+ },
+
+ /**
+ * Translates from OpenSeadragon viewer rectangle to drawer rectangle.
+ * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
+ * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
+ */
+ viewportToDrawerRectangle: function(rectangle) {
+ var topLeft = this.viewport.pixelFromPoint(rectangle.getTopLeft(), true);
+ var size = this.viewport.deltaPixelsFromPoints(rectangle.getSize(), true);
+
+ return new $.Rect(
+ topLeft.x * $.pixelDensityRatio,
+ topLeft.y * $.pixelDensityRatio,
+ size.x * $.pixelDensityRatio,
+ size.y * $.pixelDensityRatio
+ );
+ },
+
/**
* Draws the given tile.
* @param {OpenSeadragon.Tile} tile - The tile to draw.
* @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
* drawingHandler({context, tile, rendered})
+ * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
* where rendered
is the context with the pre-drawn image.
*/
- drawTile: function( tile, drawingHandler ) {
+ drawTile: function( tile, drawingHandler, useSketch ) {
$.console.assert(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
if ( this.useCanvas ) {
+ var context = this._getContext( useSketch );
// TODO do this in a more performant way
// specifically, don't save,rotate,restore every time we draw a tile
if( this.viewport.degrees !== 0 ) {
- this._offsetForRotation( tile, this.viewport.degrees );
- tile.drawCanvas( this.context, drawingHandler );
- this._restoreRotationChanges( tile );
+ this._offsetForRotation( tile, this.viewport.degrees, useSketch );
+ tile.drawCanvas( context, drawingHandler );
+ this._restoreRotationChanges( tile, useSketch );
} else {
- tile.drawCanvas( this.context, drawingHandler );
+ tile.drawCanvas( context, drawingHandler );
}
} else {
tile.drawHTML( this.canvas );
}
},
+ _getContext: function( useSketch ) {
+ var context = this.context;
+ if ( useSketch ) {
+ if (this.sketchCanvas === null) {
+ this.sketchCanvas = document.createElement( "canvas" );
+ this.sketchCanvas.width = this.canvas.width;
+ this.sketchCanvas.height = this.canvas.height;
+ this.sketchContext = this.sketchCanvas.getContext( "2d" );
+ }
+ context = this.sketchContext;
+ }
+ return context;
+ },
+
// private
- saveContext: function() {
+ saveContext: function( useSketch ) {
if (!this.useCanvas) {
return;
}
+ this._getContext( useSketch ).save();
+ },
+
+ // private
+ restoreContext: function( useSketch ) {
+ if (!this.useCanvas) {
+ return;
+ }
+
+ this._getContext( useSketch ).restore();
+ },
+
+ // private
+ setClip: function(rect, useSketch) {
+ if (!this.useCanvas) {
+ return;
+ }
+
+ var context = this._getContext( useSketch );
+ context.beginPath();
+ context.rect(rect.x, rect.y, rect.width, rect.height);
+ context.clip();
+ },
+
+ // private
+ drawRectangle: function(rect, fillStyle, useSketch) {
+ if (!this.useCanvas) {
+ return;
+ }
+
+ var context = this._getContext( useSketch );
+ context.save();
+ context.fillStyle = fillStyle;
+ context.fillRect(rect.x, rect.y, rect.width, rect.height);
+ context.restore();
+ },
+
+ /**
+ * Blends the sketch canvas in the main canvas.
+ * @param {Float} opacity The opacity of the blending.
+ * @returns {undefined}
+ */
+ blendSketch: function(opacity) {
+ if (!this.useCanvas || !this.sketchCanvas) {
+ return;
+ }
+
this.context.save();
- },
-
- // private
- restoreContext: function() {
- if (!this.useCanvas) {
- return;
- }
-
+ this.context.globalAlpha = opacity;
+ this.context.drawImage(this.sketchCanvas, 0, 0);
this.context.restore();
},
// private
- setClip: function(rect) {
- if (!this.useCanvas) {
+ drawDebugInfo: function( tile, count, i ){
+ if ( !this.useCanvas ) {
return;
}
- this.context.beginPath();
- this.context.rect(rect.x, rect.y, rect.width, rect.height);
- this.context.clip();
- },
+ var context = this.context;
+ context.save();
+ context.lineWidth = 2 * $.pixelDensityRatio;
+ context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
+ context.strokeStyle = this.debugGridColor;
+ context.fillStyle = this.debugGridColor;
- // private
- drawDebugInfo: function( tile, count, i ){
- if ( this.useCanvas ) {
- this.context.save();
- this.context.lineWidth = 2 * $.pixelDensityRatio;
- this.context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
- this.context.strokeStyle = this.debugGridColor;
- this.context.fillStyle = this.debugGridColor;
-
- if ( this.viewport.degrees !== 0 ) {
- this._offsetForRotation( tile, this.canvas, this.context, this.viewport.degrees );
- }
-
- this.context.strokeRect(
- tile.position.x * $.pixelDensityRatio,
- tile.position.y * $.pixelDensityRatio,
- tile.size.x * $.pixelDensityRatio,
- tile.size.y * $.pixelDensityRatio
- );
-
- var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
- var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
-
- // Rotate the text the right way around.
- this.context.translate( tileCenterX, tileCenterY );
- this.context.rotate( Math.PI / 180 * -this.viewport.degrees );
- this.context.translate( -tileCenterX, -tileCenterY );
-
- if( tile.x === 0 && tile.y === 0 ){
- this.context.fillText(
- "Zoom: " + this.viewport.getZoom(),
- tile.position.x * $.pixelDensityRatio,
- (tile.position.y - 30) * $.pixelDensityRatio
- );
- this.context.fillText(
- "Pan: " + this.viewport.getBounds().toString(),
- tile.position.x * $.pixelDensityRatio,
- (tile.position.y - 20) * $.pixelDensityRatio
- );
- }
- this.context.fillText(
- "Level: " + tile.level,
- (tile.position.x + 10) * $.pixelDensityRatio,
- (tile.position.y + 20) * $.pixelDensityRatio
- );
- this.context.fillText(
- "Column: " + tile.x,
- (tile.position.x + 10) * $.pixelDensityRatio,
- (tile.position.y + 30) * $.pixelDensityRatio
- );
- this.context.fillText(
- "Row: " + tile.y,
- (tile.position.x + 10) * $.pixelDensityRatio,
- (tile.position.y + 40) * $.pixelDensityRatio
- );
- this.context.fillText(
- "Order: " + i + " of " + count,
- (tile.position.x + 10) * $.pixelDensityRatio,
- (tile.position.y + 50) * $.pixelDensityRatio
- );
- this.context.fillText(
- "Size: " + tile.size.toString(),
- (tile.position.x + 10) * $.pixelDensityRatio,
- (tile.position.y + 60) * $.pixelDensityRatio
- );
- this.context.fillText(
- "Position: " + tile.position.toString(),
- (tile.position.x + 10) * $.pixelDensityRatio,
- (tile.position.y + 70) * $.pixelDensityRatio
- );
-
- if ( this.viewport.degrees !== 0 ) {
- this._restoreRotationChanges( tile, this.canvas, this.context );
- }
- this.context.restore();
+ if ( this.viewport.degrees !== 0 ) {
+ this._offsetForRotation( tile, this.viewport.degrees );
}
+
+ context.strokeRect(
+ tile.position.x * $.pixelDensityRatio,
+ tile.position.y * $.pixelDensityRatio,
+ tile.size.x * $.pixelDensityRatio,
+ tile.size.y * $.pixelDensityRatio
+ );
+
+ var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
+ var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
+
+ // Rotate the text the right way around.
+ context.translate( tileCenterX, tileCenterY );
+ context.rotate( Math.PI / 180 * -this.viewport.degrees );
+ context.translate( -tileCenterX, -tileCenterY );
+
+ if( tile.x === 0 && tile.y === 0 ){
+ context.fillText(
+ "Zoom: " + this.viewport.getZoom(),
+ tile.position.x * $.pixelDensityRatio,
+ (tile.position.y - 30) * $.pixelDensityRatio
+ );
+ context.fillText(
+ "Pan: " + this.viewport.getBounds().toString(),
+ tile.position.x * $.pixelDensityRatio,
+ (tile.position.y - 20) * $.pixelDensityRatio
+ );
+ }
+ context.fillText(
+ "Level: " + tile.level,
+ (tile.position.x + 10) * $.pixelDensityRatio,
+ (tile.position.y + 20) * $.pixelDensityRatio
+ );
+ context.fillText(
+ "Column: " + tile.x,
+ (tile.position.x + 10) * $.pixelDensityRatio,
+ (tile.position.y + 30) * $.pixelDensityRatio
+ );
+ context.fillText(
+ "Row: " + tile.y,
+ (tile.position.x + 10) * $.pixelDensityRatio,
+ (tile.position.y + 40) * $.pixelDensityRatio
+ );
+ context.fillText(
+ "Order: " + i + " of " + count,
+ (tile.position.x + 10) * $.pixelDensityRatio,
+ (tile.position.y + 50) * $.pixelDensityRatio
+ );
+ context.fillText(
+ "Size: " + tile.size.toString(),
+ (tile.position.x + 10) * $.pixelDensityRatio,
+ (tile.position.y + 60) * $.pixelDensityRatio
+ );
+ context.fillText(
+ "Position: " + tile.position.toString(),
+ (tile.position.x + 10) * $.pixelDensityRatio,
+ (tile.position.y + 70) * $.pixelDensityRatio
+ );
+
+ if ( this.viewport.degrees !== 0 ) {
+ this._restoreRotationChanges( tile );
+ }
+ context.restore();
},
// private
debugRect: function(rect) {
if ( this.useCanvas ) {
- this.context.save();
- this.context.lineWidth = 2 * $.pixelDensityRatio;
- this.context.strokeStyle = this.debugGridColor;
- this.context.fillStyle = this.debugGridColor;
+ var context = this.context;
+ context.save();
+ context.lineWidth = 2 * $.pixelDensityRatio;
+ context.strokeStyle = this.debugGridColor;
+ context.fillStyle = this.debugGridColor;
- this.context.strokeRect(
+ context.strokeRect(
rect.x * $.pixelDensityRatio,
rect.y * $.pixelDensityRatio,
rect.width * $.pixelDensityRatio,
rect.height * $.pixelDensityRatio
);
- this.context.restore();
+ context.restore();
}
},
// private
- _offsetForRotation: function( tile, degrees ){
+ _offsetForRotation: function( tile, degrees, useSketch ){
var cx = this.canvas.width / 2,
- cy = this.canvas.height / 2,
- px = tile.position.x - cx,
- py = tile.position.y - cy;
+ cy = this.canvas.height / 2;
- this.context.save();
+ var context = this._getContext( useSketch );
+ context.save();
- this.context.translate(cx, cy);
- this.context.rotate( Math.PI / 180 * degrees);
- tile.position.x = px;
- tile.position.y = py;
+ context.translate(cx, cy);
+ context.rotate( Math.PI / 180 * degrees);
+ context.translate(-cx, -cy);
},
// private
- _restoreRotationChanges: function( tile ){
- var cx = this.canvas.width / 2,
- cy = this.canvas.height / 2,
- px = tile.position.x + cx,
- py = tile.position.y + cy;
-
- tile.position.x = px;
- tile.position.y = py;
-
- this.context.restore();
+ _restoreRotationChanges: function( tile, useSketch ){
+ var context = this._getContext( useSketch );
+ context.restore();
},
// private
diff --git a/src/iiiftilesource.js b/src/iiiftilesource.js
index ca755b9f..bf3da020 100644
--- a/src/iiiftilesource.js
+++ b/src/iiiftilesource.js
@@ -55,14 +55,19 @@ $.IIIFTileSource = function( options ){
options.tileSizePerScaleFactor = {};
// N.B. 2.0 renamed scale_factors to scaleFactors
- if ( this.tile_width ) {
+ if ( this.tile_width && this.tile_height ) {
+ options.tileWidth = this.tile_width;
+ options.tileHeight = this.tile_height;
+ } else if ( this.tile_width ) {
options.tileSize = this.tile_width;
} else if ( this.tile_height ) {
options.tileSize = this.tile_height;
} else if ( this.tiles ) {
// Version 2.0 forwards
if ( this.tiles.length == 1 ) {
- options.tileSize = this.tiles[0].width;
+ options.tileWidth = this.tiles[0].width;
+ // Use height if provided, otherwise assume square tiles and use width.
+ options.tileHeight = this.tiles[0].height || this.tiles[0].width;
this.scale_factors = this.tiles[0].scaleFactors;
} else {
// Multiple tile sizes at different levels
@@ -71,13 +76,15 @@ $.IIIFTileSource = function( options ){
for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) {
var scaleFactor = this.tiles[t].scaleFactors[sf];
this.scale_factors.push(scaleFactor);
- options.tileSizePerScaleFactor[scaleFactor] = this.tiles[t].width;
+ options.tileSizePerScaleFactor[scaleFactor] = {
+ width: this.tiles[t].width,
+ height: this.tiles[t].height || this.tiles[t].width
+ };
}
}
}
} else {
// use the largest of tileOptions that is smaller than the short dimension
-
var shortDim = Math.min( this.height, this.width ),
tileOptions = [256,512,1024],
smallerTiles = [];
@@ -94,8 +101,6 @@ $.IIIFTileSource = function( options ){
// If we're smaller than 256, just use the short side.
options.tileSize = shortDim;
}
- this.tile_width = options.tileSize; // So that 'full' gets used for
- this.tile_height = options.tileSize; // the region below
}
if ( !options.maxLevel ) {
@@ -117,6 +122,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
* @param {Object|Array} data
* @param {String} optional - url
*/
+
supports: function( data, url ) {
// Version 2.0 and forwards
if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
@@ -181,20 +187,34 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
},
/**
- * Return the tileSize for the given level.
+ * Return the tileWidth for the given level.
* @function
* @param {Number} level
- */
-
- getTileSize: function( level ){
+ */
+ getTileWidth: function( level ) {
var scaleFactor = Math.pow(2, this.maxLevel - level);
- // cache it in case any external code is going to read it directly
+
if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
- this.tileSize = this.tileSizePerScaleFactor[scaleFactor];
+ return this.tileSizePerScaleFactor[scaleFactor].width;
}
- return this.tileSize;
+ return this._tileWidth;
},
+ /**
+ * Return the tileHeight for the given level.
+ * @function
+ * @param {Number} level
+ */
+ getTileHeight: function( level ) {
+ var scaleFactor = Math.pow(2, this.maxLevel - level);
+
+ if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
+ return this.tileSizePerScaleFactor[scaleFactor].height;
+ }
+ return this._tileHeight;
+ },
+
+
/**
* Responsible for retreiving the url which will return an image for the
* region specified by the given x, y, and level components.
@@ -216,7 +236,8 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
levelHeight = Math.ceil( this.height * scale ),
//## iiif region
- tileSize,
+ tileWidth,
+ tileHeight,
iiifTileSizeWidth,
iiifTileSizeHeight,
iiifRegion,
@@ -228,9 +249,10 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifQuality,
uri;
- tileSize = this.getTileSize(level);
- iiifTileSizeWidth = Math.ceil( tileSize / scale );
- iiifTileSizeHeight = iiifTileSizeWidth;
+ tileWidth = this.getTileWidth(level);
+ tileHeight = this.getTileHeight(level);
+ iiifTileSizeWidth = Math.ceil( tileWidth / scale );
+ iiifTileSizeHeight = Math.ceil( tileHeight / scale );
if ( this['@context'].indexOf('/1.0/context.json') > -1 ||
this['@context'].indexOf('/1.1/context.json') > -1 ||
@@ -240,7 +262,7 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifQuality = "default.jpg";
}
- if ( levelWidth < tileSize && levelHeight < tileSize ){
+ if ( levelWidth < tileWidth && levelHeight < tileHeight ){
iiifSize = levelWidth + ",";
iiifRegion = 'full';
} else {
diff --git a/src/imageloader.js b/src/imageloader.js
index a61c8936..ef3a4f4f 100644
--- a/src/imageloader.js
+++ b/src/imageloader.js
@@ -51,6 +51,7 @@ function ImageJob ( options ) {
}
ImageJob.prototype = {
+ errorMsg: null,
start: function(){
var _this = this;
@@ -64,10 +65,12 @@ ImageJob.prototype = {
_this.finish( true );
};
this.image.onabort = this.image.onerror = function(){
+ _this.errorMsg = "Image load aborted";
_this.finish( false );
};
this.jobId = window.setTimeout( function(){
+ _this.errorMsg = "Image load exceeded timeout";
_this.finish( false );
}, this.timeout);
@@ -173,7 +176,7 @@ function completeJob( loader, job, callback ) {
loader.jobsInProgress++;
}
- callback( job.image );
+ callback( job.image, job.errorMsg );
}
}( OpenSeadragon ));
diff --git a/src/mousetracker.js b/src/mousetracker.js
index ec677fb7..e9b5acfc 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -133,7 +133,7 @@
*/
this.element = $.getElement( options.element );
/**
- * The number of milliseconds within which a pointer down-up event combination
+ * The number of milliseconds within which a pointer down-up event combination
* will be treated as a click gesture.
* @member {Number} clickTimeThreshold
* @memberof OpenSeadragon.MouseTracker#
@@ -244,7 +244,7 @@
// Active pointers lists. Array of GesturePointList objects, one for each pointer device type.
// GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()).
- // Active pointers are any pointer being tracked for this element which are in the hit-test area
+ // Active pointers are any pointer being tracked for this element which are in the hit-test area
// of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
activePointersLists: [],
@@ -1032,7 +1032,7 @@
$.MouseTracker.mousePointerId = "legacy-mouse";
$.MouseTracker.maxTouchPoints = 10;
}
-
+
///////////////////////////////////////////////////////////////////////////////
// Classes and typedefs
@@ -1078,7 +1078,7 @@
/**
* @class GesturePointList
* @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type.
- * Active pointers are any pointer being tracked for this element which are in the hit-test area
+ * Active pointers are any pointer being tracked for this element which are in the hit-test area
* of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
* @memberof OpenSeadragon.MouseTracker
* @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
@@ -1198,7 +1198,7 @@
return null;
}
};
-
+
///////////////////////////////////////////////////////////////////////////////
// Utility functions
@@ -1282,7 +1282,7 @@
false
);
}
-
+
clearTrackedPointers( tracker );
delegate.tracking = true;
@@ -1694,7 +1694,7 @@
/**
- * Handles 'wheel' events.
+ * Handles 'wheel' events.
* The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
*
* @private
@@ -1943,7 +1943,7 @@
handleMouseMove( tracker, event );
}
-
+
/**
* This handler is attached to the window object (on the capture phase) to emulate mouse capture.
* onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
@@ -2191,7 +2191,7 @@
var i,
touchCount = event.changedTouches.length,
gPoints = [];
-
+
for ( i = 0; i < touchCount; i++ ) {
gPoints.push( {
id: event.changedTouches[ i ].identifier,
@@ -2420,7 +2420,7 @@
*/
function startTrackingPointer( pointsList, gPoint ) {
- // If isPrimary is not known for the pointer then set it according to our rules:
+ // If isPrimary is not known for the pointer then set it according to our rules:
// true if the first pointer in the gesture, otherwise false
if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
if ( pointsList.getLength() === 0 ) {
@@ -2617,7 +2617,7 @@
* Gesture points associated with the event.
* @param {Number} buttonChanged
* The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
- * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
+ * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
* only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
*
* @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false.
@@ -2779,7 +2779,7 @@
* Gesture points associated with the event.
* @param {Number} buttonChanged
* The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
- * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
+ * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
* only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
*
* @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false.
diff --git a/src/navigator.js b/src/navigator.js
index 9f3923df..7addc5ea 100644
--- a/src/navigator.js
+++ b/src/navigator.js
@@ -108,7 +108,9 @@ $.Navigator = function( options ){
immediateRender: true,
blendTime: 0,
animationTime: 0,
- autoResize: options.autoResize
+ autoResize: options.autoResize,
+ // prevent resizing the navigator from adding unwanted space around the image
+ minZoomImageRatio: 1.0
});
options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
diff --git a/src/openseadragon.js b/src/openseadragon.js
index 8d231cbe..e457a5c1 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -204,7 +204,12 @@
* If 0, adjusts to fit viewer.
*
* @property {Number} [opacity=1]
- * Opacity of the drawer (1=opaque, 0=transparent)
+ * Default opacity of the tiled images (1=opaque, 0=transparent)
+ *
+ * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
+ * Draws a colored rectangle behind the tile if it is not loaded yet.
+ * You can pass a CSS color value like "#FF8800".
+ * When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern.
*
* @property {Number} [degrees=0]
* Initial rotation.
@@ -236,7 +241,7 @@
* @property {Number} [minZoomImageRatio=0.9]
* The minimum percentage ( expressed as a number between 0 and 1 ) of
* the viewport height or width at which the zoom out will be constrained.
- * Setting it to 0, for example will allow you to zoom out infinitly.
+ * Setting it to 0, for example will allow you to zoom out infinity.
*
* @property {Number} [maxZoomPixelRatio=1.1]
* The maximum ratio to allow a zoom-in to affect the highest level pixel
@@ -247,6 +252,9 @@
* @property {Boolean} [autoResize=true]
* Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
*
+ * @property {Boolean} [preserveImageSizeOnResize=false]
+ * Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).
+ *
* @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
*
@@ -262,7 +270,7 @@
* Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.
*
* @property {Number} [imageLoaderLimit=0]
- * The maximum number of image requests to make concurrently. By default
+ * The maximum number of image requests to make concurrently. By default
* it is set to 0 allowing the browser to make the maximum number of
* image requests in parallel as allowed by the browsers policy.
*
@@ -348,7 +356,7 @@
* @property {Boolean} [showNavigator=false]
* Set to true to make the navigator minimap appear.
*
- * @property {Boolean} [navigatorId=navigator-GENERATED DATE]
+ * @property {String} [navigatorId=navigator-GENERATED DATE]
* The ID of a div to hold the navigator minimap.
* If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored.
* If an ID is not specified, a div element will be generated and placed on top of the main image.
@@ -551,6 +559,10 @@
* If collectionMode is true, specifies how many rows the grid should have. Use 1 to make a line.
* If collectionLayout is 'vertical', specifies how many columns instead.
*
+ * @property {Number} [collectionColumns=0]
+ * If collectionMode is true, specifies how many columns the grid should have. Use 1 to make a line.
+ * If collectionLayout is 'vertical', specifies how many rows instead. Ignored if collectionRows is not set to a falsy value.
+ *
* @property {String} [collectionLayout='horizontal']
* If collectionMode is true, specifies whether to arrange vertically or horizontally.
*
@@ -982,6 +994,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
pixelsPerWheelLine: 40,
autoResize: true,
+ preserveImageSizeOnResize: false, // requires autoResize=true
//DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE
@@ -1013,10 +1026,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
navigatorRotate: true,
// INITIAL ROTATION
- degrees: 0,
+ degrees: 0,
// APPEARANCE
- opacity: 1,
+ opacity: 1,
+ placeholderFillStyle: null,
//REFERENCE STRIP SETTINGS
showReferenceStrip: false,
@@ -1029,6 +1043,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
//COLLECTION VISUALIZATION SETTINGS
collectionRows: 3, //or columns depending on layout
+ collectionColumns: 0, //columns in horizontal layout, rows in vertical layout
collectionLayout: 'horizontal', //vertical
collectionMode: false,
collectionTileSize: 800,
@@ -2030,8 +2045,40 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
request.onreadystatechange = function(){};
- if ( $.isFunction( onError ) ) {
- onError( request, e );
+ if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest
+ var xdr = new XDomainRequest();
+ if (xdr) {
+ xdr.onload = function (e) {
+ if ( $.isFunction( onSuccess ) ) {
+ onSuccess({ // Faking an xhr object
+ responseText: xdr.responseText,
+ status: 200, // XDomainRequest doesn't support status codes, so we just fake one! :/
+ statusText: 'OK'
+ });
+ }
+ };
+ xdr.onerror = function (e) {
+ if ( $.isFunction ( onError ) ) {
+ onError({ // Faking an xhr object
+ responseText: xdr.responseText,
+ status: 444, // 444 No Response
+ statusText: 'An error happened. Due to an XDomainRequest deficiency we can not extract any information about this error. Upgrade your browser.'
+ });
+ }
+ };
+ try {
+ xdr.open('GET', url);
+ xdr.send();
+ } catch (e2) {
+ if ( $.isFunction( onError ) ) {
+ onError( request, e );
+ }
+ }
+ }
+ } else {
+ if ( $.isFunction( onError ) ) {
+ onError( request, e );
+ }
}
}
},
@@ -2161,6 +2208,24 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
return $.parseXml( string );
},
+ /**
+ * Parses a JSON string into a Javascript object.
+ * @function
+ * @param {String} string
+ * @returns {Object}
+ */
+ parseJSON: function(string) {
+ if (window.JSON && window.JSON.parse) {
+ $.parseJSON = window.JSON.parse;
+ } else {
+ // Should only be used by IE8 in non standards mode
+ $.parseJSON = function(string) {
+ /*jshint evil:true*/
+ return eval('(' + string + ')');
+ };
+ }
+ return $.parseJSON(string);
+ },
/**
* Reports whether the image format is supported for tiling in this
diff --git a/src/overlay.js b/src/overlay.js
index 0bdbbf34..0484ead6 100644
--- a/src/overlay.js
+++ b/src/overlay.js
@@ -282,7 +282,10 @@
style.left = position.x + "px";
style.top = position.y + "px";
style.position = "absolute";
- style.display = 'block';
+
+ if (style.display != 'none') {
+ style.display = 'block';
+ }
if ( scales ) {
style.width = size.x + "px";
diff --git a/src/tile.js b/src/tile.js
index 756d9d4a..48f3503c 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -67,7 +67,7 @@ $.Tile = function(level, x, y, bounds, exists, url) {
this.y = y;
/**
* Where this tile fits, in normalized coordinates
- * @member {OpenSeadragon.Point} bounds
+ * @member {OpenSeadragon.Rect} bounds
* @memberof OpenSeadragon.Tile#
*/
this.bounds = bounds;
@@ -190,7 +190,14 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* @param {Element} container
*/
drawHTML: function( container ) {
- if ( !this.loaded || !this.image ) {
+ if (!this.cacheImageRecord) {
+ $.console.warn(
+ '[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
+ this.toString());
+ return;
+ }
+
+ if ( !this.loaded ) {
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
@@ -203,8 +210,7 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
if ( !this.element ) {
this.element = $.makeNeutralElement( "div" );
- this.imgElement = $.makeNeutralElement( "img" );
- this.imgElement.src = this.url;
+ this.imgElement = this.cacheImageRecord.getImage().cloneNode();
this.imgElement.style.msInterpolationMode = "nearest-neighbor";
this.imgElement.style.width = "100%";
this.imgElement.style.height = "100%";
@@ -239,17 +245,18 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
var position = this.position,
size = this.size,
- rendered,
- canvas;
+ rendered;
if (!this.cacheImageRecord) {
- $.console.warn('[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', this.toString());
+ $.console.warn(
+ '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
+ this.toString());
return;
}
rendered = this.cacheImageRecord.getRenderedContext();
- if ( !this.loaded || !( this.image || rendered) ){
+ if ( !this.loaded || !rendered ){
$.console.warn(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
@@ -276,19 +283,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
}
- if(!rendered){
- canvas = document.createElement( 'canvas' );
- canvas.width = this.image.width;
- canvas.height = this.image.height;
- rendered = canvas.getContext('2d');
- rendered.drawImage( this.image, 0, 0 );
- this.cacheImageRecord.setRenderedContext(rendered);
- //since we are caching the prerendered image on a canvas
- //allow the image to not be held in memory
- this.image = null;
- }
-
- // This gives the application a chance to make image manipulation changes as we are rendering the image
+ // This gives the application a chance to make image manipulation
+ // changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
context.drawImage(
@@ -318,7 +314,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
this.element = null;
this.imgElement = null;
- this.image = null;
this.loaded = false;
this.loading = false;
}
diff --git a/src/tilecache.js b/src/tilecache.js
index 45344a05..281709c8 100644
--- a/src/tilecache.js
+++ b/src/tilecache.js
@@ -63,10 +63,23 @@ ImageRecord.prototype = {
},
getRenderedContext: function() {
+ if (!this._renderedContext) {
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = this._image.width;
+ canvas.height = this._image.height;
+ this._renderedContext = canvas.getContext('2d');
+ this._renderedContext.drawImage( this._image, 0, 0 );
+ //since we are caching the prerendered image on a canvas
+ //allow the image to not be held in memory
+ this._image = null;
+ }
return this._renderedContext;
},
setRenderedContext: function(renderedContext) {
+ $.console.error("ImageRecord.setRenderedContext is deprecated. " +
+ "The rendered context should be created by the ImageRecord " +
+ "itself when calling ImageRecord.getRenderedContext.");
this._renderedContext = renderedContext;
},
@@ -126,6 +139,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
* may temporarily surpass that number, but should eventually come back down to the max specified.
* @param {Object} options - Tile info.
* @param {OpenSeadragon.Tile} options.tile - The tile to cache.
+ * @param {Image} options.image - The image of the tile to cache.
* @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
* @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
* function will release an old tile. The cutoff option specifies a tile level at or below which
@@ -135,7 +149,6 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
$.console.assert( options, "[TileCache.cacheTile] options is required" );
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
$.console.assert( options.tile.url, "[TileCache.cacheTile] options.tile.url is required" );
- $.console.assert( options.tile.image, "[TileCache.cacheTile] options.tile.image is required" );
$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0;
@@ -143,8 +156,9 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
var imageRecord = this._imagesLoaded[options.tile.url];
if (!imageRecord) {
+ $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
- image: options.tile.image
+ image: options.image
});
this._imagesLoadedCount++;
@@ -158,6 +172,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
var worstTile = null;
var worstTileIndex = -1;
+ var worstTileRecord = null;
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
@@ -169,6 +184,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
} else if ( !worstTile ) {
worstTile = prevTile;
worstTileIndex = i;
+ worstTileRecord = prevTileRecord;
continue;
}
@@ -181,11 +197,12 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
( prevTime == worstTime && prevLevel > worstLevel ) ) {
worstTile = prevTile;
worstTileIndex = i;
+ worstTileRecord = prevTileRecord;
}
}
if ( worstTile && worstTileIndex >= 0 ) {
- this._unloadTile(worstTile);
+ this._unloadTile(worstTileRecord);
insertionIndex = worstTileIndex;
}
}
@@ -206,7 +223,7 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
tileRecord = this._tilesLoaded[ i ];
if ( tileRecord.tiledImage === tiledImage ) {
- this._unloadTile(tileRecord.tile);
+ this._unloadTile(tileRecord);
this._tilesLoaded.splice( i, 1 );
i--;
}
@@ -220,8 +237,11 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
},
// private
- _unloadTile: function(tile) {
- $.console.assert(tile, '[TileCache._unloadTile] tile is required');
+ _unloadTile: function(tileRecord) {
+ $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');
+ var tile = tileRecord.tile;
+ var tiledImage = tileRecord.tiledImage;
+
tile.unload();
tile.cacheImageRecord = null;
@@ -232,6 +252,20 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
delete this._imagesLoaded[tile.url];
this._imagesLoadedCount--;
}
+
+ /**
+ * Triggered when a tile has just been unloaded from memory.
+ *
+ * @event tile-unloaded
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.
+ * @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.
+ */
+ tiledImage.viewer.raiseEvent("tile-unloaded", {
+ tile: tile,
+ tiledImage: tiledImage
+ });
}
};
diff --git a/src/tiledimage.js b/src/tiledimage.js
index 405989b7..4aaef1db 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -64,7 +64,9 @@
* @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
+ * @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
+ * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
*/
$.TiledImage = function( options ) {
@@ -126,21 +128,23 @@ $.TiledImage = function( options ) {
coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
lastDrawn: [], // An unordered list of Tiles drawn last frame.
lastResetTime: 0, // Last time for which the tiledImage was reset.
- _midDraw: false, // Is the tiledImage currently updating the viewport?
- _needsDraw: true, // Does the tiledImage need to update the viewport again?
+ _midDraw: false, // Is the tiledImage currently updating the viewport?
+ _needsDraw: true, // Does the tiledImage need to update the viewport again?
//configurable settings
- springStiffness: $.DEFAULT_SETTINGS.springStiffness,
- animationTime: $.DEFAULT_SETTINGS.animationTime,
- minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
- wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
- wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
- immediateRender: $.DEFAULT_SETTINGS.immediateRender,
- blendTime: $.DEFAULT_SETTINGS.blendTime,
- alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
- minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
- debugMode: $.DEFAULT_SETTINGS.debugMode,
- crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy
+ springStiffness: $.DEFAULT_SETTINGS.springStiffness,
+ animationTime: $.DEFAULT_SETTINGS.animationTime,
+ minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
+ wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
+ wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
+ immediateRender: $.DEFAULT_SETTINGS.immediateRender,
+ blendTime: $.DEFAULT_SETTINGS.blendTime,
+ alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
+ minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
+ debugMode: $.DEFAULT_SETTINGS.debugMode,
+ crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
+ placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
+ opacity: $.DEFAULT_SETTINGS.opacity
}, options );
@@ -402,6 +406,83 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
);
},
+ /**
+ * Convert pixel coordinates relative to the viewer element to image
+ * coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ viewerElementToImageCoordinates: function( pixel ) {
+ var point = this.viewport.pointFromPixel( pixel, true );
+ return this.viewportToImageCoordinates( point );
+ },
+
+ /**
+ * Convert pixel coordinates relative to the image to
+ * viewer element coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ imageToViewerElementCoordinates: function( pixel ) {
+ var point = this.imageToViewportCoordinates( pixel );
+ return this.viewport.pixelFromPoint( point, true );
+ },
+
+ /**
+ * Convert pixel coordinates relative to the window to image coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ windowToImageCoordinates: function( pixel ) {
+ var viewerCoordinates = pixel.minus(
+ OpenSeadragon.getElementPosition( this.viewer.element ));
+ return this.viewerElementToImageCoordinates( viewerCoordinates );
+ },
+
+ /**
+ * Convert image coordinates to pixel coordinates relative to the window.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ imageToWindowCoordinates: function( pixel ) {
+ var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
+ return viewerCoordinates.plus(
+ OpenSeadragon.getElementPosition( this.viewer.element ));
+ },
+
+ /**
+ * Convert a viewport zoom to an image zoom.
+ * Image zoom: ratio of the original image size to displayed image size.
+ * 1 means original image size, 0.5 half size...
+ * Viewport zoom: ratio of the displayed image's width to viewport's width.
+ * 1 means identical width, 2 means image's width is twice the viewport's width...
+ * @function
+ * @param {Number} viewportZoom The viewport zoom
+ * @returns {Number} imageZoom The image zoom
+ */
+ viewportToImageZoom: function( viewportZoom ) {
+ var ratio = this._scaleSpring.current.value *
+ this.viewport._containerInnerSize.x / this.source.dimensions.x;
+ return ratio * viewportZoom ;
+ },
+
+ /**
+ * Convert an image zoom to a viewport zoom.
+ * Image zoom: ratio of the original image size to displayed image size.
+ * 1 means original image size, 0.5 half size...
+ * Viewport zoom: ratio of the displayed image's width to viewport's width.
+ * 1 means identical width, 2 means image's width is twice the viewport's width...
+ * Note: not accurate with multi-image.
+ * @function
+ * @param {Number} imageZoom The image zoom
+ * @returns {Number} viewportZoom The viewport zoom
+ */
+ imageToViewportZoom: function( imageZoom ) {
+ var ratio = this._scaleSpring.current.value *
+ this.viewport._containerInnerSize.x / this.source.dimensions.x;
+ return imageZoom / ratio;
+ },
+
/**
* Sets the TiledImage's position in the world.
* @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.
@@ -484,6 +565,21 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._needsDraw = true;
},
+ /**
+ * @returns {Number} The TiledImage's current opacity.
+ */
+ getOpacity: function() {
+ return this.opacity;
+ },
+
+ /**
+ * @param {Number} opacity Opacity the tiled image should be drawn at.
+ */
+ setOpacity: function(opacity) {
+ this.opacity = opacity;
+ this._needsDraw = true;
+ },
+
// private
_setScale: function(scale, immediately) {
var sameTarget = (this._scaleSpring.target.value === scale);
@@ -696,8 +792,6 @@ function updateViewport( tiledImage ) {
// Load the new 'best' tile
if ( best ) {
loadTile( tiledImage, best, currentTime );
- // because we haven't finished drawing, so
- tiledImage._needsDraw = true;
}
}
@@ -843,13 +937,8 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
if (!tile.loaded) {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
if (imageRecord) {
- tile.loaded = true;
- tile.image = imageRecord.getImage();
-
- tiledImage._tileCache.cacheTile({
- tile: tile,
- tiledImage: tiledImage
- });
+ var image = imageRecord.getImage();
+ setTileLoaded(tiledImage, tile, image);
}
}
@@ -922,8 +1011,8 @@ function loadTile( tiledImage, tile, time ) {
tiledImage._imageLoader.addJob({
src: tile.url,
crossOriginPolicy: tiledImage.crossOriginPolicy,
- callback: function( image ){
- onTileLoad( tiledImage, tile, time, image );
+ callback: function( image, errorMsg ){
+ onTileLoad( tiledImage, tile, time, image, errorMsg );
},
abort: function() {
tile.loading = false;
@@ -931,9 +1020,9 @@ function loadTile( tiledImage, tile, time ) {
});
}
-function onTileLoad( tiledImage, tile, time, image ) {
+function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
if ( !image ) {
- $.console.log( "Tile %s failed to load: %s", tile, tile.url );
+ $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
if( !tiledImage.debugMode ){
tile.loading = false;
tile.exists = false;
@@ -946,16 +1035,9 @@ function onTileLoad( tiledImage, tile, time, image ) {
}
var finish = function() {
- tile.loading = false;
- tile.loaded = true;
- tile.image = image;
-
- var cutoff = Math.ceil( Math.log( tiledImage.source.getTileSize(tile.level) ) / Math.log( 2 ) );
- tiledImage._tileCache.cacheTile({
- tile: tile,
- cutoff: cutoff,
- tiledImage: tiledImage
- });
+ var cutoff = Math.ceil( Math.log(
+ tiledImage.source.getTileWidth(tile.level) ) / Math.log( 2 ) );
+ setTileLoaded(tiledImage, tile, image, cutoff);
};
// Check if we're mid-update; this can happen on IE8 because image load events for
@@ -966,10 +1048,55 @@ function onTileLoad( tiledImage, tile, time, image ) {
// Wait until after the update, in case caching unloads any tiles
window.setTimeout( finish, 1);
}
-
- tiledImage._needsDraw = true;
}
+function setTileLoaded(tiledImage, tile, image, cutoff) {
+ var increment = 0;
+
+ function getCompletionCallback() {
+ increment++;
+ return completionCallback;
+ }
+
+ function completionCallback() {
+ increment--;
+ if (increment === 0) {
+ tile.loading = false;
+ tile.loaded = true;
+ tiledImage._tileCache.cacheTile({
+ image: image,
+ tile: tile,
+ cutoff: cutoff,
+ tiledImage: tiledImage
+ });
+ tiledImage._needsDraw = true;
+ }
+ }
+
+ /**
+ * Triggered when a tile has just been loaded in memory. That means that the
+ * image has been downloaded and can be modified before being drawn to the canvas.
+ *
+ * @event tile-loaded
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {Image} image - The image of the tile.
+ * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
+ * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
+ * @property {function} getCompletionCallback - A function giving a callback to call
+ * when the asynchronous processing of the image is done. The image will be
+ * marked as entirely loaded when the callback has been called once for each
+ * call to getCompletionCallback.
+ */
+ tiledImage.viewer.raiseEvent("tile-loaded", {
+ tile: tile,
+ tiledImage: tiledImage,
+ image: image,
+ getCompletionCallback: getCompletionCallback
+ });
+ // In case the completion callback is never called, we at least force it once.
+ getCompletionCallback()();
+}
function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
var boundsTL = tile.bounds.getTopLeft();
@@ -1148,42 +1275,49 @@ function compareTiles( previousBest, tile ) {
return previousBest;
}
-function drawTiles( tiledImage, lastDrawn ){
+function drawTiles( tiledImage, lastDrawn ) {
var i,
- tile,
- tileKey,
- viewer,
- viewport,
- position,
- tileSource;
+ tile;
+
+ if ( tiledImage.opacity <= 0 ) {
+ drawDebugInfo( tiledImage, lastDrawn );
+ return;
+ }
+ var useSketch = tiledImage.opacity < 1;
+ if ( useSketch ) {
+ tiledImage._drawer._clear( true );
+ }
var usedClip = false;
- if (tiledImage._clip) {
- tiledImage._drawer.saveContext();
+ if ( tiledImage._clip ) {
+ tiledImage._drawer.saveContext(useSketch);
+
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
- var topLeft = tiledImage.viewport.pixelFromPoint(box.getTopLeft(), true);
- var size = tiledImage.viewport.deltaPixelsFromPoints(box.getSize(), true);
- box = new OpenSeadragon.Rect(topLeft.x * $.pixelDensityRatio,
- topLeft.y * $.pixelDensityRatio,
- size.x * $.pixelDensityRatio,
- size.y * $.pixelDensityRatio);
- tiledImage._drawer.setClip(box);
+ var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
+ tiledImage._drawer.setClip(clipRect, useSketch);
+
usedClip = true;
}
+ if ( tiledImage.placeholderFillStyle && lastDrawn.length === 0 ) {
+ var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
+
+ var fillStyle = null;
+ if ( typeof tiledImage.placeholderFillStyle === "function" ) {
+ fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context);
+ }
+ else {
+ fillStyle = tiledImage.placeholderFillStyle;
+ }
+
+ tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);
+ }
+
for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
tile = lastDrawn[ i ];
- tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler );
+ tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch );
tile.beingDrawn = true;
- if( tiledImage.debugMode ){
- try{
- tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i );
- }catch(e){
- $.console.error(e);
- }
- }
-
if( tiledImage.viewer ){
/**
* - Needs documentation -
@@ -1203,8 +1337,26 @@ function drawTiles( tiledImage, lastDrawn ){
}
}
- if (usedClip) {
- tiledImage._drawer.restoreContext();
+ if ( usedClip ) {
+ tiledImage._drawer.restoreContext( useSketch );
+ }
+
+ if ( useSketch ) {
+ tiledImage._drawer.blendSketch( tiledImage.opacity );
+ }
+ drawDebugInfo( tiledImage, lastDrawn );
+}
+
+function drawDebugInfo( tiledImage, lastDrawn ) {
+ if( tiledImage.debugMode ) {
+ for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
+ var tile = lastDrawn[ i ];
+ try {
+ tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i );
+ } catch(e) {
+ $.console.error(e);
+ }
+ }
}
}
diff --git a/src/tilesource.js b/src/tilesource.js
index 1c0d29d2..65841d25 100644
--- a/src/tilesource.js
+++ b/src/tilesource.js
@@ -73,6 +73,11 @@
* The size of the tiles to assumed to make up each pyramid layer in pixels.
* Tile size determines the point at which the image pyramid must be
* divided into a matrix of smaller images.
+ * Use options.tileWidth and options.tileHeight to support non-square tiles.
+ * @param {Number} [options.tileWidth]
+ * The width of the tiles to assumed to make up each pyramid layer in pixels.
+ * @param {Number} [options.tileHeight]
+ * The height of the tiles to assumed to make up each pyramid layer in pixels.
* @param {Number} [options.tileOverlap]
* The number of pixels each tile is expected to overlap touching tiles.
* @param {Number} [options.minLevel]
@@ -137,13 +142,6 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
* @member {OpenSeadragon.Point} dimensions
* @memberof OpenSeadragon.TileSource#
*/
- /**
- * The size of the image tiles used to compose the image.
- * Please note that tileSize may be deprecated in a future release.
- * Instead the getTileSize(level) function should be used.
- * @member {Number} tileSize
- * @memberof OpenSeadragon.TileSource#
- */
/**
* The overlap in pixels each tile shares with its adjacent neighbors.
* @member {Number} tileOverlap
@@ -174,7 +172,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//async mechanism set some safe defaults first
this.aspectRatio = 1;
this.dimensions = new $.Point( 10, 10 );
- this.tileSize = 0;
+ this._tileWidth = 0;
+ this._tileHeight = 0;
this.tileOverlap = 0;
this.minLevel = 0;
this.maxLevel = 0;
@@ -191,7 +190,29 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.aspectRatio = ( options.width && options.height ) ?
( options.width / options.height ) : 1;
this.dimensions = new $.Point( options.width, options.height );
- this.tileSize = options.tileSize ? options.tileSize : 0;
+
+ if ( this.tileSize ){
+ this._tileWidth = this._tileHeight = this.tileSize;
+ delete this.tileSize;
+ } else {
+ if( this.tileWidth ){
+ // We were passed tileWidth in options, but we want to rename it
+ // with a leading underscore to make clear that it is not safe to directly modify it
+ this._tileWidth = this.tileWidth;
+ delete this.tileWidth;
+ } else {
+ this._tileWidth = 0;
+ }
+
+ if( this.tileHeight ){
+ // See note above about renaming this.tileWidth
+ this._tileHeight = this.tileHeight;
+ delete this.tileHeight;
+ } else {
+ this._tileHeight = 0;
+ }
+ }
+
this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
this.minLevel = options.minLevel ? options.minLevel : 0;
this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
@@ -212,16 +233,42 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
$.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
+ getTileSize: function( level ) {
+ $.console.error(
+ "[TileSource.getTileSize] is deprecated." +
+ "Use TileSource.getTileWidth() and TileSource.getTileHeight() instead"
+ );
+ return this._tileWidth;
+ },
+
/**
- * Return the tileSize for a given level.
- * Subclasses should override this if tileSizes can be different at different levels
+ * Return the tileWidth for a given level.
+ * Subclasses should override this if tileWidth can be different at different levels
* such as in IIIFTileSource. Code should use this function rather than reading
- * from .tileSize directly. tileSize may be deprecated in a future release.
+ * from ._tileWidth directly.
* @function
* @param {Number} level
*/
- getTileSize: function( level ) {
- return this.tileSize;
+ getTileWidth: function( level ) {
+ if (!this._tileWidth) {
+ return this.getTileSize(level);
+ }
+ return this._tileWidth;
+ },
+
+ /**
+ * Return the tileHeight for a given level.
+ * Subclasses should override this if tileHeight can be different at different levels
+ * such as in IIIFTileSource. Code should use this function rather than reading
+ * from ._tileHeight directly.
+ * @function
+ * @param {Number} level
+ */
+ getTileHeight: function( level ) {
+ if (!this._tileHeight) {
+ return this.getTileSize(level);
+ }
+ return this._tileHeight;
},
/**
@@ -250,8 +297,8 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
*/
getNumTiles: function( level ) {
var scale = this.getLevelScale( level ),
- x = Math.ceil( scale * this.dimensions.x / this.getTileSize(level) ),
- y = Math.ceil( scale * this.dimensions.y / this.getTileSize(level) );
+ x = Math.ceil( scale * this.dimensions.x / this.getTileWidth(level) ),
+ y = Math.ceil( scale * this.dimensions.y / this.getTileHeight(level) );
return new $.Point( x, y );
},
@@ -277,10 +324,15 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
var i,
tilesPerSide,
tiles;
+
for( i = this.minLevel; i < this.maxLevel; i++ ){
tiles = this.getNumTiles( i );
- tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.getTileSize(i) );
- if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){
+ tilesPerSide = new $.Point(
+ Math.floor( rect.x / this.getTileWidth(i) ),
+ Math.floor( rect.y / this.getTileHeight(i) )
+ );
+
+ if( tiles.x + 1 >= tilesPerSide.x || tiles.y + 1 >= tilesPerSide.y ){
break;
}
}
@@ -293,9 +345,9 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
* @param {OpenSeadragon.Point} point
*/
getTileAtPoint: function( level, point ) {
- var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ),
- tx = Math.floor( pixel.x / this.getTileSize(level) ),
- ty = Math.floor( pixel.y / this.getTileSize(level) );
+ var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level) ),
+ tx = Math.floor( pixel.x / this.getTileWidth(level) ),
+ ty = Math.floor( pixel.y / this.getTileHeight(level) );
return new $.Point( tx, ty );
},
@@ -308,11 +360,12 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
*/
getTileBounds: function( level, x, y ) {
var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
- tileSize = this.getTileSize(level),
- px = ( x === 0 ) ? 0 : tileSize * x - this.tileOverlap,
- py = ( y === 0 ) ? 0 : tileSize * y - this.tileOverlap,
- sx = tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
- sy = tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
+ tileWidth = this.getTileWidth(level),
+ tileHeight = this.getTileHeight(level),
+ px = ( x === 0 ) ? 0 : tileWidth * x - this.tileOverlap,
+ py = ( y === 0 ) ? 0 : tileHeight * y - this.tileOverlap,
+ sx = tileWidth + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
+ sy = tileHeight + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
scale = 1.0 / dimensionsScaled.x;
sx = Math.min( sx, dimensionsScaled.x - px );
@@ -560,8 +613,7 @@ function processResponse( xhr ){
data = xhr.responseText;
}
}else if( responseText.match(/\s*[\{\[].*/) ){
- /*jshint evil:true*/
- data = eval( '('+responseText+')' );
+ data = $.parseJSON(responseText);
}else{
data = responseText;
}
diff --git a/src/viewer.js b/src/viewer.js
index 7b269350..bca49e5c 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -374,7 +374,6 @@ $.Viewer = function( options ) {
viewer: this,
viewport: this.viewport,
element: this.canvas,
- opacity: this.opacity,
debugGridColor: this.debugGridColor
});
@@ -1073,6 +1072,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
};
/**
* Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
+ * Note: the pre-full-screen event is not raised when the user is exiting
+ * full-screen mode by pressing the Esc key. In that case, consider using
+ * the full-screen, pre-full-page or full-page events.
*
* @event pre-full-screen
* @memberof OpenSeadragon.Viewer
@@ -1191,6 +1193,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* named 'getTileUrl', it is treated as a custom TileSource.
* @param {Number} [options.index] The index of the item. Added on top of
* all other items if not specified.
+ * @param {Boolean} [options.replace=false] If true, the item at options.index will be
+ * removed and the new item is added in its place. options.tileSource will be
+ * interpreted and fetched if necessary before the old item is removed to avoid leaving
+ * a gap in the world.
* @param {Number} [options.x=0] The X position for the image in viewport coordinates.
* @param {Number} [options.y=0] The Y position for the image in viewport coordinates.
* @param {Number} [options.width=1] The width for the image in viewport coordinates.
@@ -1198,6 +1204,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
* (portions of the image outside of this area will not be visible). Only works on
* browsers that support the HTML5 canvas.
+ * @param {Number} [options.opacity] Opacity the tiled image should be drawn at by default.
* @param {Function} [options.success] A function that gets called when the image is
* successfully added. It's passed the event object which contains a single property:
* "item", the resulting TiledImage.
@@ -1206,17 +1213,31 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* and "source" properties.
* @param {Boolean} [options.collectionImmediately=false] If collectionMode is on,
* specifies whether to snap to the new arrangement immediately or to animate to it.
+ * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @fires OpenSeadragon.World.event:add-item
* @fires OpenSeadragon.Viewer.event:add-item-failed
*/
addTiledImage: function( options ) {
$.console.assert(options, "[Viewer.addTiledImage] options is required");
$.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required");
+ $.console.assert(!options.replace || (options.index > -1 && options.index < this.world.getItemCount()),
+ "[Viewer.addTiledImage] if options.replace is used, options.index must be a valid index in Viewer.world");
var _this = this;
+ if (options.replace) {
+ options.replaceItem = _this.world.getItemAt(options.index);
+ }
+
this._hideMessage();
+ if (options.placeholderFillStyle === undefined) {
+ options.placeholderFillStyle = this.placeholderFillStyle;
+ }
+ if (options.opacity === undefined) {
+ options.opacity = this.opacity;
+ }
+
var myQueueItem = {
options: options
};
@@ -1272,6 +1293,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this._loadQueue.splice(0, 1);
+ if (queueItem.options.replace) {
+ var newIndex = _this.world.getIndexOfItem(options.replaceItem);
+ if (newIndex != -1) {
+ options.index = newIndex;
+ }
+ _this.world.removeItem(options.replaceItem);
+ }
+
tiledImage = new $.TiledImage({
viewer: _this,
source: queueItem.tileSource,
@@ -1284,6 +1313,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
width: queueItem.options.width,
height: queueItem.options.height,
clip: queueItem.options.clip,
+ placeholderFillStyle: queueItem.options.placeholderFillStyle,
+ opacity: queueItem.options.opacity,
springStiffness: _this.springStiffness,
animationTime: _this.animationTime,
minZoomImageRatio: _this.minZoomImageRatio,
@@ -1305,6 +1336,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
_this.world.arrange({
immediately: queueItem.options.collectionImmediately,
rows: _this.collectionRows,
+ columns: _this.collectionColumns,
layout: _this.collectionLayout,
tileSize: _this.collectionTileSize,
tileMargin: _this.collectionTileMargin
@@ -1697,7 +1729,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* is closed which include when changing page.
* @method
* @param {Element|String|Object} element - A reference to an element or an id for
- * the element which will overlayed. Or an Object specifying the configuration for the overlay
+ * the element which will be overlayed. Or an Object specifying the configuration for the overlay
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the
@@ -1757,6 +1789,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* Updates the overlay represented by the reference to the element or
* element id moving it to the new location, relative to the new placement.
* @method
+ * @param {Element|String} element - A reference to an element or an id for
+ * the element which is overlayed.
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the
@@ -1980,8 +2014,7 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
if ( tileSource.match( /\s*<.*/ ) ) {
tileSource = $.parseXml( tileSource );
} else if ( tileSource.match( /\s*[\{\[].*/ ) ) {
- /*jshint evil:true*/
- tileSource = eval( '(' + tileSource + ')' );
+ tileSource = $.parseJSON(tileSource);
}
}
@@ -2206,7 +2239,7 @@ function onCanvasKeyDown( event ) {
if ( event.shift ) {
this.viewport.zoomBy(1.1);
} else {
- this.viewport.panBy(new $.Point(0, -0.05));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
}
this.viewport.applyConstraints();
return false;
@@ -2214,16 +2247,16 @@ function onCanvasKeyDown( event ) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
} else {
- this.viewport.panBy(new $.Point(0, 0.05));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
}
this.viewport.applyConstraints();
return false;
case 37://left arrow
- this.viewport.panBy(new $.Point(-0.05, 0));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
this.viewport.applyConstraints();
return false;
case 39://right arrow
- this.viewport.panBy(new $.Point(0.05, 0));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints();
return false;
default:
@@ -2255,7 +2288,7 @@ function onCanvasKeyPress( event ) {
if ( event.shift ) {
this.viewport.zoomBy(1.1);
} else {
- this.viewport.panBy(new $.Point(0, -0.05));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
}
this.viewport.applyConstraints();
return false;
@@ -2264,16 +2297,16 @@ function onCanvasKeyPress( event ) {
if ( event.shift ) {
this.viewport.zoomBy(0.9);
} else {
- this.viewport.panBy(new $.Point(0, 0.05));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
}
this.viewport.applyConstraints();
return false;
case 97://a
- this.viewport.panBy(new $.Point(-0.05, 0));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
this.viewport.applyConstraints();
return false;
case 100://d
- this.viewport.panBy(new $.Point(0.05, 0));
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
this.viewport.applyConstraints();
return false;
default:
@@ -2811,13 +2844,31 @@ function updateOnce( viewer ) {
return;
}
+ var containerSize;
if ( viewer.autoResize ) {
- var containerSize = _getSafeElemSize( viewer.container );
+ containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
- // maintain image position
- var oldBounds = viewer.viewport.getBounds();
- var oldCenter = viewer.viewport.getCenter();
- resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
+ if ( viewer.preserveImageSizeOnResize ) {
+ var prevContainerSize = THIS[ viewer.hash ].prevContainerSize;
+ var bounds = viewer.viewport.getBounds(true);
+ var deltaX = (containerSize.x - prevContainerSize.x);
+ var deltaY = (containerSize.y - prevContainerSize.y);
+ var viewportDiff = viewer.viewport.deltaPointsFromPixels(new OpenSeadragon.Point(deltaX, deltaY), true);
+ viewer.viewport.resize(new OpenSeadragon.Point(containerSize.x, containerSize.y), false);
+
+ // Keep the center of the image in the center and just adjust the amount of image shown
+ bounds.width += viewportDiff.x;
+ bounds.height += viewportDiff.y;
+ bounds.x -= (viewportDiff.x / 2);
+ bounds.y -= (viewportDiff.y / 2);
+ viewer.viewport.fitBoundsWithConstraints(bounds, true);
+ }
+ else {
+ // maintain image position
+ var oldBounds = viewer.viewport.getBounds();
+ var oldCenter = viewer.viewport.getCenter();
+ resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
+ }
THIS[ viewer.hash ].prevContainerSize = containerSize;
THIS[ viewer.hash ].forceRedraw = true;
}
@@ -2914,19 +2965,15 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter
viewport.resize( containerSize, true );
- // We try to remove blanks as much as possible
- var worldBounds = viewer.world.getHomeBounds();
- var newWidth = oldBounds.width <= worldBounds.width ? oldBounds.width : worldBounds.width;
- var newHeight = oldBounds.height <= worldBounds.height ?
- oldBounds.height : worldBounds.height;
-
var newBounds = new $.Rect(
- oldCenter.x - ( newWidth / 2.0 ),
- oldCenter.y - ( newHeight / 2.0 ),
- newWidth,
- newHeight
- );
- viewport.fitBounds( newBounds, true );
+ oldCenter.x - ( oldBounds.width / 2.0 ),
+ oldCenter.y - ( oldBounds.height / 2.0 ),
+ oldBounds.width,
+ oldBounds.height
+ );
+
+ // let the viewport decide if the bounds are too big or too small
+ viewport.fitBoundsWithConstraints( newBounds, true );
}
function drawWorld( viewer ) {
diff --git a/src/world.js b/src/world.js
index c9e65225..73b28a67 100644
--- a/src/world.js
+++ b/src/world.js
@@ -281,6 +281,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.
* @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.
* @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.
+ * @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.
* @fires OpenSeadragon.World.event:metrics-change
@@ -290,10 +291,16 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
var immediately = options.immediately || false;
var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;
var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;
+ var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;
var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;
var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;
var increment = tileSize + tileMargin;
- var wrap = Math.ceil(this._items.length / rows);
+ var wrap;
+ if (!options.rows && columns) {
+ wrap = columns;
+ } else {
+ wrap = Math.ceil(this._items.length / rows);
+ }
var x = 0;
var y = 0;
var item, box, width, height, position;
diff --git a/test/coverage.html b/test/coverage.html
index 9d61c48f..65cc2818 100644
--- a/test/coverage.html
+++ b/test/coverage.html
@@ -71,6 +71,7 @@
+