diff --git a/.gitignore b/.gitignore
index aaaabf8c..282b4dc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,6 @@ build/
sftp-config.json
coverage/
temp/
-.idea
\ No newline at end of file
+.idea
+/nbproject/private/
+.directory
diff --git a/Gruntfile.js b/Gruntfile.js
index 06511e31..8c7ff60a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -39,6 +39,7 @@ module.exports = function(grunt) {
"src/osmtilesource.js",
"src/tmstilesource.js",
"src/legacytilesource.js",
+ "src/imagetilesource.js",
"src/tilesourcecollection.js",
"src/button.js",
"src/buttongroup.js",
diff --git a/changelog.txt b/changelog.txt
index 0d06a9b5..44b640c8 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,42 +1,54 @@
OPENSEADRAGON CHANGELOG
=======================
-2.1.0: (in progress)
+2.1.1: (in progress)
+
+* Tile edge smoothing at high zoom (#764)
+* Fixed issue with reference strip popping up virtual keyboard on mobile devices (#779)
+* Now supporting rotation in the Rect class (#782)
+* Drag outside of iframe now works better, as long as both pages are on the same domain (#790)
+* Coordinate conversion now takes rotation into account (#796)
+* Support tile-less IIIF as per LegacyTileSource (#816)
+* You can now give an empty string to the tabIndex option (#805)
+
+2.1.0:
+
* BREAKING CHANGE: the tile does not hold a reference to its image anymore. Only the tile cache keep a reference to images.
* BREAKING CHANGE: TileSource.tileSize no longer exists; use TileSource.getTileWidth() and TileSource.getTileHeight() instead.
* 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)
+* Changed resize behaviour to prevent "snapping" to world bounds when constraints allow more space (#711)
* 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 new tile source for simple images: ImageTileSource (#760)
+* Optimized adding large numbers of items to the world with collectionMode (#735)
+* Registers as an AMD module where possible (#719)
+* 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)
+* Added 'tile-load-failed' event (#725)
+* Added additional coordinates conversion methods to TiledImage (#662)
+* Added `preserveImageSizeOnResize` option (#666)
+* Added collectionColumns as a configuration parameter (#680)
+* Added option in addTiledImage to replace tiledImage at index (#706)
+* Added autoRefigureSizes flag to World for optimizing mass rearrangements (#715)
+* You can now change viewport margins after the viewer is created (#721)
+* Added a patch to help slow down the scroll devices that fire too fast (#754)
+* Fixed flickering tiles with useCanvas=false when no cache is used (#661)
+* 'display: none' no longer gets reset on overlays during draw (#668)
+* Better error reporting for tile load failures (#679)
* 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)
-* Registers as an AMD module where possible (#719)
-* Added autoRefigureSizes flag to World for optimizing mass rearrangements (#715)
-* Added 'tile-load-failed' event (#725)
* Fixed issue with tiledImages loading tiles at every level instead of just the best level (#728)
* Fixed placeholderFillStyle flicker (#727)
* Fix for Chrome (v45) issue that key is sometimes undefined outside of the for-in loop (#730)
* World.removeAll now cancels any in-flight image loads; same for Viewer.open and Viewer.close (#734)
-* Optimized adding large numbers of items to the world with collectionMode (#735)
* Fixed overlays position (use rounding instead of flooring and ceiling) (#741)
* Fixed issue with including overlays in your tileSources array when creating/opening in the viewer (#745)
* Fixed issue in iOS devices that would cause all touch events to fail after a Multitasking Gesture was triggered (#744)
* Fixed an issue with TiledImage setPosition/setWidth/setHeight not reliably triggering a redraw (#720)
-* You can now change viewport margins after the viewer is created (#721)
* Fixed zooming in with plus key on a Swedish keyboard (#763)
-* Added a patch to help slow down the scroll devices that fire too fast (#754)
2.0.0:
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 00000000..c57a3e0d
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,20 @@
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=4
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=4
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none
+auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project
+auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.continuationIndentSize=4
+auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.indent-shift-width=4
+auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.spaceBeforeAnonMethodDeclParen=false
+auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.indent-shift-width=2
+auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.spaces-per-tab=2
+auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=src
+browser.autorefresh.Chromium.INTEGRATED=true
+browser.highlightselection.Chromium.INTEGRATED=true
+file.reference.openseadragon-openseadragon=.
+files.encoding=UTF-8
+site.root.folder=${file.reference.openseadragon-openseadragon}
+start.file=test/demo/basic.html
+web.context.root=/
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 00000000..53613f5c
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,9 @@
+
+
+ org.netbeans.modules.web.clientproject
+
+
+ openseadragon
+
+
+
diff --git a/package.json b/package.json
index d2081b58..3e0b81f2 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,19 @@
{
- "name": "OpenSeadragon",
- "version": "2.0.0",
+ "name": "openseadragon",
+ "version": "2.1.0",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
+ "keywords": ["image", "zoom", "pan", "openseadragon", "seadragon", "deepzoom", "dzi", "iiif", "osm", "tms"],
+ "homepage": "http://openseadragon.github.io/",
+ "bugs": {
+ "url": "https://github.com/openseadragon/openseadragon/issues"
+ },
+ "license": "BSD-3-Clause",
+ "files": ["build/openseadragon/"],
+ "main": "build/openseadragon/openseadragon.js",
+ "repository": {
+ "type" : "git",
+ "url" : "https://github.com/openseadragon/openseadragon.git"
+ },
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.5.0",
diff --git a/src/drawer.js b/src/drawer.js
index 919057d7..633d6086 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -267,13 +267,14 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
},
/**
- * Translates from OpenSeadragon viewer rectangle to drawer rectangle.
+ * Scale from OpenSeadragon viewer rectangle to drawer rectangle
+ * (ignoring rotation)
* @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);
+ var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
+ var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
@@ -290,22 +291,17 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* 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.
+ * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
+ * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
*/
- drawTile: function( tile, drawingHandler, useSketch ) {
+ drawTile: function(tile, drawingHandler, useSketch, scale, translate) {
$.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, useSketch );
- tile.drawCanvas( context, drawingHandler );
- this._restoreRotationChanges( tile, useSketch );
- } else {
- tile.drawCanvas( context, drawingHandler );
- }
+ if (this.useCanvas) {
+ var context = this._getContext(useSketch);
+ scale = scale || 1;
+ tile.drawCanvas(context, drawingHandler, scale, translate);
} else {
tile.drawHTML( this.canvas );
}
@@ -371,17 +367,35 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
/**
* Blends the sketch canvas in the main canvas.
* @param {Float} opacity The opacity of the blending.
+ * @param {Float} [scale=1] The scale at which tiles were drawn on the sketch. Default is 1.
+ * Use scale to draw at a lower scale and then enlarge onto the main canvas.
+ * @param OpenSeadragon.Point} [translate] A translation vector that was used to draw the tiles
* @returns {undefined}
*/
- blendSketch: function(opacity, compositeOperation) {
+ blendSketch: function(opacity, scale, translate, compositeOperation) {
if (!this.useCanvas || !this.sketchCanvas) {
return;
}
+ scale = scale || 1;
+ var position = translate instanceof $.Point ?
+ translate :
+ new $.Point(0, 0);
this.context.save();
this.context.globalAlpha = opacity;
this.context.globalCompositeOperation = compositeOperation;
- this.context.drawImage(this.sketchCanvas, 0, 0);
+ this.context.drawImage(
+ this.sketchCanvas,
+ position.x,
+ position.y,
+ this.sketchCanvas.width * scale,
+ this.sketchCanvas.height * scale,
+ 0,
+ 0,
+ this.canvas.width,
+ this.canvas.height
+ );
+>>>>>>> a0a44dbeb5e3030e0acecf108efc19dbd53aaec2
this.context.restore();
},
@@ -399,7 +413,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
context.fillStyle = this.debugGridColor;
if ( this.viewport.degrees !== 0 ) {
- this._offsetForRotation( tile, this.viewport.degrees );
+ this._offsetForRotation(this.viewport.degrees);
}
context.strokeRect(
@@ -461,7 +475,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
);
if ( this.viewport.degrees !== 0 ) {
- this._restoreRotationChanges( tile );
+ this._restoreRotationChanges();
}
context.restore();
},
@@ -487,21 +501,21 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
},
// private
- _offsetForRotation: function( tile, degrees, useSketch ){
- var cx = this.canvas.width / 2,
- cy = this.canvas.height / 2;
+ _offsetForRotation: function(degrees, useSketch) {
+ var cx = this.canvas.width / 2;
+ var cy = this.canvas.height / 2;
- var context = this._getContext( useSketch );
+ var context = this._getContext(useSketch);
context.save();
context.translate(cx, cy);
- context.rotate( Math.PI / 180 * degrees);
+ context.rotate(Math.PI / 180 * degrees);
context.translate(-cx, -cy);
},
// private
- _restoreRotationChanges: function( tile, useSketch ){
- var context = this._getContext( useSketch );
+ _restoreRotationChanges: function(useSketch) {
+ var context = this._getContext(useSketch);
context.restore();
},
diff --git a/src/iiiftilesource.js b/src/iiiftilesource.js
index bf3da020..4584f1cd 100644
--- a/src/iiiftilesource.js
+++ b/src/iiiftilesource.js
@@ -36,8 +36,8 @@
/**
* @class IIIFTileSource
- * @classdesc A client implementation of the International Image Interoperability
- * Format: Image API 1.0 - 2.0
+ * @classdesc A client implementation of the International Image Interoperability Framework
+ * Format: Image API 1.0 - 2.1
*
* @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
@@ -83,7 +83,7 @@ $.IIIFTileSource = function( options ){
}
}
}
- } else {
+ } else if ( canBeTiled(options.profile) ) {
// use the largest of tileOptions that is smaller than the short dimension
var shortDim = Math.min( this.height, this.width ),
tileOptions = [256,512,1024],
@@ -101,13 +101,32 @@ $.IIIFTileSource = function( options ){
// If we're smaller than 256, just use the short side.
options.tileSize = shortDim;
}
+ } else if (this.sizes && this.sizes.length > 0) {
+ // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array.
+ // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the
+ // LegacyTileSource instead of performing IIIF tiling.
+ this.emulateLegacyImagePyramid = true;
+
+ options.levels = constructLevels( this );
+ // use the largest available size to define tiles
+ $.extend( true, options, {
+ width: options.levels[ options.levels.length - 1 ].width,
+ height: options.levels[ options.levels.length - 1 ].height,
+ tileSize: Math.max( options.height, options.width ),
+ tileOverlap: 0,
+ minLevel: 0,
+ maxLevel: options.levels.length - 1
+ });
+ this.levels = options.levels;
+ } else {
+ $.console.error("Nothing in the info.json to construct image pyramids from");
}
- if ( !options.maxLevel ) {
- if ( !this.scale_factors ) {
- options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) );
+ if (!options.maxLevel && !this.emulateLegacyImagePyramid) {
+ if (!this.scale_factors) {
+ options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2)));
} else {
- options.maxLevel = Math.floor( Math.pow( Math.max.apply(null, this.scale_factors), 0.5) );
+ options.maxLevel = Math.floor(Math.pow(Math.max.apply(null, this.scale_factors), 0.5));
}
}
@@ -192,6 +211,11 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
* @param {Number} level
*/
getTileWidth: function( level ) {
+
+ if(this.emulateLegacyImagePyramid) {
+ return $.TileSource.prototype.getTileWidth.call(this, level);
+ }
+
var scaleFactor = Math.pow(2, this.maxLevel - level);
if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
@@ -206,6 +230,11 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
* @param {Number} level
*/
getTileHeight: function( level ) {
+
+ if(this.emulateLegacyImagePyramid) {
+ return $.TileSource.prototype.getTileHeight.call(this, level);
+ }
+
var scaleFactor = Math.pow(2, this.maxLevel - level);
if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
@@ -214,9 +243,61 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
return this._tileHeight;
},
+ /**
+ * @function
+ * @param {Number} level
+ */
+ getLevelScale: function ( level ) {
+
+ if(this.emulateLegacyImagePyramid) {
+ var levelScale = NaN;
+ if (this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel) {
+ levelScale =
+ this.levels[level].width /
+ this.levels[this.maxLevel].width;
+ }
+ return levelScale;
+ }
+
+ return $.TileSource.prototype.getLevelScale.call(this, level);
+ },
/**
- * Responsible for retreiving the url which will return an image for the
+ * @function
+ * @param {Number} level
+ */
+ getNumTiles: function( level ) {
+
+ if(this.emulateLegacyImagePyramid) {
+ var scale = this.getLevelScale(level);
+ if (scale) {
+ return new $.Point(1, 1);
+ } else {
+ return new $.Point(0, 0);
+ }
+ }
+
+ return $.TileSource.prototype.getNumTiles.call(this, level);
+ },
+
+
+ /**
+ * @function
+ * @param {Number} level
+ * @param {OpenSeadragon.Point} point
+ */
+ getTileAtPoint: function( level, point ) {
+
+ if(this.emulateLegacyImagePyramid) {
+ return new $.Point(0, 0);
+ }
+
+ return $.TileSource.prototype.getTileAtPoint.call(this, level, point);
+ },
+
+
+ /**
+ * Responsible for retrieving the url which will return an image for the
* region specified by the given x, y, and level components.
* @function
* @param {Number} level - z index
@@ -226,6 +307,14 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
*/
getTileUrl: function( level, x, y ){
+ if(this.emulateLegacyImagePyramid) {
+ var url = null;
+ if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
+ url = this.levels[ level ].url;
+ }
+ return url;
+ }
+
//# constants
var IIIF_ROTATION = '0',
//## get the scale (level as a decimal)
@@ -280,6 +369,40 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
});
+ /**
+ * Determine whether arbitrary tile requests can be made against a service with the given profile
+ * @function
+ * @param {object} profile - IIIF profile object
+ * @throws {Error}
+ */
+ function canBeTiled (profile ) {
+ var level0Profiles = [
+ "http://library.stanford.edu/iiif/image-api/compliance.html#level0",
+ "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0",
+ "http://iiif.io/api/image/2/level0.json"
+ ];
+ var isLevel0 = (level0Profiles.indexOf(profile[0]) != -1);
+ return !isLevel0 || (profile.indexOf("sizeByW") != -1);
+ }
+
+ /**
+ * Build the legacy pyramid URLs (one tile per level)
+ * @function
+ * @param {object} options - infoJson
+ * @throws {Error}
+ */
+ function constructLevels(options) {
+ var levels = [];
+ for(var i=0; i= this.minLevel && level <= this.maxLevel) {
+ levelScale =
+ this.levels[level].width /
+ this.levels[this.maxLevel].width;
+ }
+ return levelScale;
+ },
+ /**
+ * @function
+ * @param {Number} level
+ */
+ getNumTiles: function (level) {
+ var scale = this.getLevelScale(level);
+ if (scale) {
+ return new $.Point(1, 1);
+ } else {
+ return new $.Point(0, 0);
+ }
+ },
+ /**
+ * @function
+ * @param {Number} level
+ * @param {OpenSeadragon.Point} point
+ */
+ getTileAtPoint: function (level, point) {
+ return new $.Point(0, 0);
+ },
+ /**
+ * Retrieves a tile url
+ * @function
+ * @param {Number} level Level of the tile
+ * @param {Number} x x coordinate of the tile
+ * @param {Number} y y coordinate of the tile
+ */
+ getTileUrl: function (level, x, y) {
+ var url = null;
+ if (level >= this.minLevel && level <= this.maxLevel) {
+ url = this.levels[level].url;
+ }
+ return url;
+ },
+ /**
+ * Retrieves a tile context 2D
+ * @function
+ * @param {Number} level Level of the tile
+ * @param {Number} x x coordinate of the tile
+ * @param {Number} y y coordinate of the tile
+ */
+ getContext2D: function (level, x, y) {
+ var context = null;
+ if (level >= this.minLevel && level <= this.maxLevel) {
+ context = this.levels[level].context2D;
+ }
+ return context;
+ },
+
+ // private
+ //
+ // Builds the differents levels of the pyramid if possible
+ // (i.e. if canvas API enabled and no canvas tainting issue).
+ _buildLevels: function () {
+ var levels = [{
+ url: this._image.src,
+ width: this._image.naturalWidth,
+ height: this._image.naturalHeight
+ }];
+
+ if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
+ // We don't need the image anymore. Allows it to be GC.
+ delete this._image;
+ return levels;
+ }
+
+ var currentWidth = this._image.naturalWidth;
+ var currentHeight = this._image.naturalHeight;
+
+ var bigCanvas = document.createElement("canvas");
+ var bigContext = bigCanvas.getContext("2d");
+
+ bigCanvas.width = currentWidth;
+ bigCanvas.height = currentHeight;
+ bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight);
+ // We cache the context of the highest level because the browser
+ // is a lot faster at downsampling something it already has
+ // downsampled before.
+ levels[0].context2D = bigContext;
+ // We don't need the image anymore. Allows it to be GC.
+ delete this._image;
+
+ if ($.isCanvasTainted(bigCanvas)) {
+ // If the canvas is tainted, we can't compute the pyramid.
+ return levels;
+ }
+
+ // We build smaller levels until either width or height becomes
+ // 1 pixel wide.
+ while (currentWidth >= 2 && currentHeight >= 2) {
+ currentWidth = Math.floor(currentWidth / 2);
+ currentHeight = Math.floor(currentHeight / 2);
+ var smallCanvas = document.createElement("canvas");
+ var smallContext = smallCanvas.getContext("2d");
+ smallCanvas.width = currentWidth;
+ smallCanvas.height = currentHeight;
+ smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight);
+
+ levels.splice(0, 0, {
+ context2D: smallContext,
+ width: currentWidth,
+ height: currentHeight
+ });
+
+ bigCanvas = smallCanvas;
+ bigContext = smallContext;
+ }
+ return levels;
+ }
+ });
+
+}(OpenSeadragon));
diff --git a/src/mousetracker.js b/src/mousetracker.js
index 0a66cfee..a57993de 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -1367,6 +1367,14 @@
eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
// We emulate mouse capture by hanging listeners on the document object.
// (Note we listen on the capture phase so the captured handlers will get called first)
+ if (isInIframe && canAccessEvents(window.top)) {
+ $.addEvent(
+ window.top,
+ eventParams.upName,
+ eventParams.upHandler,
+ true
+ );
+ }
$.addEvent(
$.MouseTracker.captureElement,
eventParams.upName,
@@ -1402,6 +1410,14 @@
eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
// We emulate mouse capture by hanging listeners on the document object.
// (Note we listen on the capture phase so the captured handlers will get called first)
+ if (isInIframe && canAccessEvents(window.top)) {
+ $.removeEvent(
+ window.top,
+ eventParams.upName,
+ eventParams.upHandler,
+ true
+ );
+ }
$.removeEvent(
$.MouseTracker.captureElement,
eventParams.moveName,
@@ -3248,5 +3264,29 @@
} );
}
}
+
+ // True if inside an iframe, otherwise false.
+ // @member {Boolean} isInIframe
+ // @private
+ // @inner
+ var isInIframe = (function() {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+ })();
+
+ // @function
+ // @private
+ // @inner
+ // @returns {Boolean} True if the target has access rights to events, otherwise false.
+ function canAccessEvents (target) {
+ try {
+ return target.addEventListener && target.removeEventListener;
+ } catch (e) {
+ return false;
+ }
+ }
} ( OpenSeadragon ) );
diff --git a/src/navigator.js b/src/navigator.js
index 7addc5ea..9fac3637 100644
--- a/src/navigator.js
+++ b/src/navigator.js
@@ -223,12 +223,6 @@ $.Navigator = function( options ){
}
});
- this.addHandler("reset-size", function() {
- if (_this.viewport) {
- _this.viewport.goHome(true);
- }
- });
-
viewer.world.addHandler("item-index-change", function(event) {
var item = _this.world.getItemAt(event.previousIndex);
_this.world.setItemIndex(item, event.newIndex);
@@ -307,8 +301,8 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
if( viewport && this.viewport ) {
bounds = viewport.getBounds( true );
- topleft = this.viewport.pixelFromPoint( bounds.getTopLeft(), false );
- bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight(), false )
+ topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false);
+ bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
.minus( this.totalBorderWidths );
//update style for navigator-box
@@ -378,7 +372,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
*/
function onCanvasClick( event ) {
if ( event.quick && this.viewer.viewport ) {
- this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ).rotate( -this.viewer.viewport.degrees, this.viewer.viewport.getHomeBounds().getCenter() ) );
+ this.viewer.viewport.panTo(this.viewport.pointFromPixel(event.position));
this.viewer.viewport.applyConstraints();
}
}
diff --git a/src/openseadragon.js b/src/openseadragon.js
index f3f14c79..16587057 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -254,6 +254,11 @@
* image though it is less effective visually if the HTML5 Canvas is not
* availble on the viewing device.
*
+ * @property {Number} [smoothTileEdgesMinZoom=1.1]
+ * A zoom percentage ( where 1 is 100% ) of the highest resolution level.
+ * When zoomed in beyond this value alternative compositing will be used to
+ * smooth out the edges between tiles. This will have a performance impact.
+ *
* @property {Boolean} [autoResize=true]
* Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
*
@@ -849,6 +854,23 @@ if (typeof define === 'function' && define.amd) {
canvasElement.getContext( '2d' ) );
}());
+ /**
+ * Test whether the submitted canvas is tainted or not.
+ * @argument {Canvas} canvas The canvas to test.
+ * @returns {Boolean} True if the canvas is tainted.
+ */
+ $.isCanvasTainted = function(canvas) {
+ var isTainted = false;
+ try {
+ // We test if the canvas is tainted by retrieving data from it.
+ // An exception will be raised if the canvas is tainted.
+ var data = canvas.getContext('2d').getImageData(0, 0, 1, 1);
+ } catch (e) {
+ isTainted = true;
+ }
+ return isTainted;
+ };
+
/**
* A ratio comparing the device screen's pixel density to the canvas's backing store pixel density. Defaults to 1 if canvas isn't supported by the browser.
* @member {Number} pixelDensityRatio
@@ -1010,6 +1032,7 @@ if (typeof define === 'function' && define.amd) {
immediateRender: false,
minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity
maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
+ smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
pixelsPerWheelLine: 40,
autoResize: true,
preserveImageSizeOnResize: false, // requires autoResize=true
diff --git a/src/point.js b/src/point.js
index 1ceef296..ebddfffe 100644
--- a/src/point.js
+++ b/src/point.js
@@ -179,14 +179,46 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
* From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point
* @function
* @param {Number} degress to rotate around the pivot.
- * @param {OpenSeadragon.Point} pivot Point about which to rotate.
+ * @param {OpenSeadragon.Point} [pivot=(0,0)] Point around which to rotate.
+ * Defaults to the origin.
* @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot
*/
- rotate: function ( degrees, pivot ) {
- var angle = degrees * Math.PI / 180.0,
- x = Math.cos( angle ) * ( this.x - pivot.x ) - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x,
- y = Math.sin( angle ) * ( this.x - pivot.x ) + Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y;
- return new $.Point( x, y );
+ rotate: function (degrees, pivot) {
+ pivot = pivot || new $.Point(0, 0);
+ var cos;
+ var sin;
+ // Avoid float computations when possible
+ if (degrees % 90 === 0) {
+ var d = degrees % 360;
+ if (d < 0) {
+ d += 360;
+ }
+ switch (d) {
+ case 0:
+ cos = 1;
+ sin = 0;
+ break;
+ case 90:
+ cos = 0;
+ sin = 1;
+ break;
+ case 180:
+ cos = -1;
+ sin = 0;
+ break;
+ case 270:
+ cos = 0;
+ sin = -1;
+ break;
+ }
+ } else {
+ var angle = degrees * Math.PI / 180.0;
+ cos = Math.cos(angle);
+ sin = Math.sin(angle);
+ }
+ var x = cos * (this.x - pivot.x) - sin * (this.y - pivot.y) + pivot.x;
+ var y = sin * (this.x - pivot.x) + cos * (this.y - pivot.y) + pivot.y;
+ return new $.Point(x, y);
},
/**
diff --git a/src/rectangle.js b/src/rectangle.js
index 5d3495af..2cfd4060 100644
--- a/src/rectangle.js
+++ b/src/rectangle.js
@@ -32,46 +32,82 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-(function( $ ){
+(function($) {
/**
* @class Rect
- * @classdesc A Rectangle really represents a 2x2 matrix where each row represents a
- * 2 dimensional vector component, the first is (x,y) and the second is
- * (width, height). The latter component implies the equation of a simple
- * plane.
+ * @classdesc A Rectangle is described by it top left coordinates (x, y), width,
+ * height and degrees of rotation around (x, y).
+ * Note that the coordinate system used is the one commonly used with images:
+ * x increases when going to the right
+ * y increases when going to the bottom
+ * degrees increases clockwise with 0 being the horizontal
+ *
+ * The constructor normalizes the rectangle to always have 0 <= degrees < 90
*
* @memberof OpenSeadragon
- * @param {Number} x The vector component 'x'.
- * @param {Number} y The vector component 'y'.
- * @param {Number} width The vector component 'height'.
- * @param {Number} height The vector component 'width'.
+ * @param {Number} [x=0] The vector component 'x'.
+ * @param {Number} [y=0] The vector component 'y'.
+ * @param {Number} [width=0] The vector component 'width'.
+ * @param {Number} [height=0] The vector component 'height'.
+ * @param {Number} [degrees=0] Rotation of the rectangle around (x,y) in degrees.
*/
-$.Rect = function( x, y, width, height ) {
+$.Rect = function(x, y, width, height, degrees) {
/**
* The vector component 'x'.
* @member {Number} x
* @memberof OpenSeadragon.Rect#
*/
- this.x = typeof ( x ) == "number" ? x : 0;
+ this.x = typeof(x) === "number" ? x : 0;
/**
* The vector component 'y'.
* @member {Number} y
* @memberof OpenSeadragon.Rect#
*/
- this.y = typeof ( y ) == "number" ? y : 0;
+ this.y = typeof(y) === "number" ? y : 0;
/**
* The vector component 'width'.
* @member {Number} width
* @memberof OpenSeadragon.Rect#
*/
- this.width = typeof ( width ) == "number" ? width : 0;
+ this.width = typeof(width) === "number" ? width : 0;
/**
* The vector component 'height'.
* @member {Number} height
* @memberof OpenSeadragon.Rect#
*/
- this.height = typeof ( height ) == "number" ? height : 0;
+ this.height = typeof(height) === "number" ? height : 0;
+
+ this.degrees = typeof(degrees) === "number" ? degrees : 0;
+
+ // Normalizes the rectangle.
+ this.degrees = this.degrees % 360;
+ if (this.degrees < 0) {
+ this.degrees += 360;
+ }
+ var newTopLeft, newWidth;
+ if (this.degrees >= 270) {
+ newTopLeft = this.getTopRight();
+ this.x = newTopLeft.x;
+ this.y = newTopLeft.y;
+ newWidth = this.height;
+ this.height = this.width;
+ this.width = newWidth;
+ this.degrees -= 270;
+ } else if (this.degrees >= 180) {
+ newTopLeft = this.getBottomRight();
+ this.x = newTopLeft.x;
+ this.y = newTopLeft.y;
+ this.degrees -= 180;
+ } else if (this.degrees >= 90) {
+ newTopLeft = this.getBottomLeft();
+ this.x = newTopLeft.x;
+ this.y = newTopLeft.y;
+ newWidth = this.height;
+ this.height = this.width;
+ this.width = newWidth;
+ this.degrees -= 90;
+ }
};
$.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
@@ -80,7 +116,12 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* @returns {OpenSeadragon.Rect} a duplicate of this Rect
*/
clone: function() {
- return new $.Rect(this.x, this.y, this.width, this.height);
+ return new $.Rect(
+ this.x,
+ this.y,
+ this.width,
+ this.height,
+ this.degrees);
},
/**
@@ -114,10 +155,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the rectangle.
*/
getBottomRight: function() {
- return new $.Point(
- this.x + this.width,
- this.y + this.height
- );
+ return new $.Point(this.x + this.width, this.y + this.height)
+ .rotate(this.degrees, this.getTopLeft());
},
/**
@@ -128,10 +167,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the rectangle.
*/
getTopRight: function() {
- return new $.Point(
- this.x + this.width,
- this.y
- );
+ return new $.Point(this.x + this.width, this.y)
+ .rotate(this.degrees, this.getTopLeft());
},
/**
@@ -142,10 +179,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the rectangle.
*/
getBottomLeft: function() {
- return new $.Point(
- this.x,
- this.y + this.height
- );
+ return new $.Point(this.x, this.y + this.height)
+ .rotate(this.degrees, this.getTopLeft());
},
/**
@@ -158,7 +193,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
return new $.Point(
this.x + this.width / 2.0,
this.y + this.height / 2.0
- );
+ ).rotate(this.degrees, this.getTopLeft());
},
/**
@@ -168,7 +203,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the width and height of the rectangle.
*/
getSize: function() {
- return new $.Point( this.width, this.height );
+ return new $.Point(this.width, this.height);
},
/**
@@ -177,98 +212,131 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
* @return {Boolean} 'true' if all components are equal, otherwise 'false'.
*/
- equals: function( other ) {
- return ( other instanceof $.Rect ) &&
- ( this.x === other.x ) &&
- ( this.y === other.y ) &&
- ( this.width === other.width ) &&
- ( this.height === other.height );
+ equals: function(other) {
+ return (other instanceof $.Rect) &&
+ this.x === other.x &&
+ this.y === other.y &&
+ this.width === other.width &&
+ this.height === other.height &&
+ this.degrees === other.degrees;
},
/**
- * Multiply all dimensions in this Rect by a factor and return a new Rect.
+ * Multiply all dimensions (except degrees) in this Rect by a factor and
+ * return a new Rect.
* @function
* @param {Number} factor The factor to multiply vector components.
* @returns {OpenSeadragon.Rect} A new rect representing the multiplication
* of the vector components by the factor
*/
- times: function( factor ) {
- return new OpenSeadragon.Rect(
+ times: function(factor) {
+ return new $.Rect(
this.x * factor,
this.y * factor,
this.width * factor,
- this.height * factor
- );
+ this.height * factor,
+ this.degrees);
},
/**
- * Returns the smallest rectangle that will contain this and the given rectangle.
+ * Translate/move this Rect by a vector and return new Rect.
+ * @function
+ * @param {OpenSeadragon.Point} delta The translation vector.
+ * @returns {OpenSeadragon.Rect} A new rect with altered position
+ */
+ translate: function(delta) {
+ return new $.Rect(
+ this.x + delta.x,
+ this.y + delta.y,
+ this.width,
+ this.height,
+ this.degrees);
+ },
+
+ /**
+ * Returns the smallest rectangle that will contain this and the given
+ * rectangle bounding boxes.
* @param {OpenSeadragon.Rect} rect
* @return {OpenSeadragon.Rect} The new rectangle.
*/
- // ----------
union: function(rect) {
- var left = Math.min(this.x, rect.x);
- var top = Math.min(this.y, rect.y);
- var right = Math.max(this.x + this.width, rect.x + rect.width);
- var bottom = Math.max(this.y + this.height, rect.y + rect.height);
+ var thisBoundingBox = this.getBoundingBox();
+ var otherBoundingBox = rect.getBoundingBox();
- return new OpenSeadragon.Rect(left, top, right - left, bottom - top);
+ var left = Math.min(thisBoundingBox.x, otherBoundingBox.x);
+ var top = Math.min(thisBoundingBox.y, otherBoundingBox.y);
+ var right = Math.max(
+ thisBoundingBox.x + thisBoundingBox.width,
+ otherBoundingBox.x + otherBoundingBox.width);
+ var bottom = Math.max(
+ thisBoundingBox.y + thisBoundingBox.height,
+ otherBoundingBox.y + otherBoundingBox.height);
+
+ return new $.Rect(
+ left,
+ top,
+ right - left,
+ bottom - top);
},
/**
- * Rotates a rectangle around a point. Currently only 90, 180, and 270
- * degrees are supported.
+ * Rotates a rectangle around a point.
* @function
* @param {Number} degrees The angle in degrees to rotate.
* @param {OpenSeadragon.Point} pivot The point about which to rotate.
* Defaults to the center of the rectangle.
* @return {OpenSeadragon.Rect}
*/
- rotate: function( degrees, pivot ) {
- // TODO support arbitrary rotation
- var width = this.width,
- height = this.height,
- newTopLeft;
-
- degrees = ( degrees + 360 ) % 360;
- if (degrees % 90 !== 0) {
- throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
+ rotate: function(degrees, pivot) {
+ degrees = degrees % 360;
+ if (degrees === 0) {
+ return this.clone();
}
-
- if( degrees === 0 ){
- return new $.Rect(
- this.x,
- this.y,
- this.width,
- this.height
- );
+ if (degrees < 0) {
+ degrees += 360;
}
pivot = pivot || this.getCenter();
+ var newTopLeft = this.getTopLeft().rotate(degrees, pivot);
+ var newTopRight = this.getTopRight().rotate(degrees, pivot);
- switch ( degrees ) {
- case 90:
- newTopLeft = this.getBottomLeft();
- width = this.height;
- height = this.width;
- break;
- case 180:
- newTopLeft = this.getBottomRight();
- break;
- case 270:
- newTopLeft = this.getTopRight();
- width = this.height;
- height = this.width;
- break;
- default:
- newTopLeft = this.getTopLeft();
- break;
+ var diff = newTopRight.minus(newTopLeft);
+ var radians = Math.atan(diff.y / diff.x);
+ if (diff.x < 0) {
+ radians += Math.PI;
+ } else if (diff.y < 0) {
+ radians += 2 * Math.PI;
}
+ return new $.Rect(
+ newTopLeft.x,
+ newTopLeft.y,
+ this.width,
+ this.height,
+ radians / Math.PI * 180);
+ },
- newTopLeft = newTopLeft.rotate(degrees, pivot);
-
- return new $.Rect(newTopLeft.x, newTopLeft.y, width, height);
+ /**
+ * Retrieves the smallest horizontal (degrees=0) rectangle which contains
+ * this rectangle.
+ * @returns {OpenSeadrayon.Rect}
+ */
+ getBoundingBox: function() {
+ if (this.degrees === 0) {
+ return this.clone();
+ }
+ var topLeft = this.getTopLeft();
+ var topRight = this.getTopRight();
+ var bottomLeft = this.getBottomLeft();
+ var bottomRight = this.getBottomRight();
+ var minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
+ var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
+ var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
+ var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
+ return new $.Rect(
+ minX,
+ minY,
+ maxX - minX,
+ maxY - minY);
},
/**
@@ -279,13 +347,14 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
*/
toString: function() {
return "[" +
- (Math.round(this.x*100) / 100) + "," +
- (Math.round(this.y*100) / 100) + "," +
- (Math.round(this.width*100) / 100) + "x" +
- (Math.round(this.height*100) / 100) +
- "]";
+ (Math.round(this.x * 100) / 100) + "," +
+ (Math.round(this.y * 100) / 100) + "," +
+ (Math.round(this.width * 100) / 100) + "x" +
+ (Math.round(this.height * 100) / 100) + "," +
+ (Math.round(this.degrees * 100) / 100) + "deg" +
+ "]";
}
};
-}( OpenSeadragon ));
+}(OpenSeadragon));
diff --git a/src/referencestrip.js b/src/referencestrip.js
index aef4e93e..0743d365 100644
--- a/src/referencestrip.js
+++ b/src/referencestrip.js
@@ -436,7 +436,7 @@ function loadPanels( strip, viewerSize, scroll ) {
animationTime: 0
} );
- miniViewer.displayRegion = $.makeNeutralElement( "textarea" );
+ miniViewer.displayRegion = $.makeNeutralElement( "div" );
miniViewer.displayRegion.id = element.id + '-displayregion';
miniViewer.displayRegion.className = 'displayregion';
diff --git a/src/spring.js b/src/spring.js
index 2c89ee07..4e44516e 100644
--- a/src/spring.js
+++ b/src/spring.js
@@ -79,7 +79,7 @@ $.Spring = function( options ) {
$.console.assert(typeof options.springStiffness === "number" && options.springStiffness !== 0,
"[OpenSeadragon.Spring] options.springStiffness must be a non-zero number");
- $.console.assert(typeof options.animationTime === "number" && options.springStiffness !== 0,
+ $.console.assert(typeof options.animationTime === "number" && options.animationTime !== 0,
"[OpenSeadragon.Spring] options.animationTime must be a non-zero number");
if (options.exponential) {
diff --git a/src/tile.js b/src/tile.js
index ad018ba7..a07bec74 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -45,8 +45,10 @@
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
* this tile failed to load? )
* @param {String} url The URL of this tile's image.
+ * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
+ * is provided directly by the tile source.
*/
-$.Tile = function(level, x, y, bounds, exists, url) {
+$.Tile = function(level, x, y, bounds, exists, url, context2D) {
/**
* The zoom level this tile belongs to.
* @member {Number} level
@@ -83,6 +85,12 @@ $.Tile = function(level, x, y, bounds, exists, url) {
* @memberof OpenSeadragon.Tile#
*/
this.url = url;
+ /**
+ * The context2D of this tile if it is provided directly by the tile source.
+ * @member {CanvasRenderingContext2D} context2D
+ * @memberOf OpenSeadragon.Tile#
+ */
+ this.context2D = context2D;
/**
* Is this tile loaded?
* @member {Boolean} loaded
@@ -240,21 +248,23 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where rendered
is the context with the pre-drawn image.
+ * @param {Number} [scale=1] - Apply a scale to position and size
+ * @param {OpenSeadragon.Point} [translate] - A translation vector
*/
- drawCanvas: function( context, drawingHandler ) {
+ drawCanvas: function( context, drawingHandler, scale, translate ) {
- var position = this.position,
- size = this.size,
+ var position = this.position.times($.pixelDensityRatio),
+ size = this.size.times($.pixelDensityRatio),
rendered;
- if (!this.cacheImageRecord) {
+ if (!this.context2D && !this.cacheImageRecord) {
$.console.warn(
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
- rendered = this.cacheImageRecord.getRenderedContext();
+ rendered = this.context2D || this.cacheImageRecord.getRenderedContext();
if ( !this.loaded || !rendered ){
$.console.warn(
@@ -273,14 +283,15 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
//ie its done fading or fading is turned off, and if we are drawing
//an image with an alpha channel, then the only way
//to avoid seeing the tile underneath is to clear the rectangle
- if( context.globalAlpha == 1 && this.url.match('.png') ){
+ if (context.globalAlpha === 1 &&
+ (this.context2D || this.url.match('.png'))) {
//clearing only the inside of the rectangle occupied
//by the png prevents edge flikering
context.clearRect(
- (position.x * $.pixelDensityRatio)+1,
- (position.y * $.pixelDensityRatio)+1,
- (size.x * $.pixelDensityRatio)-2,
- (size.y * $.pixelDensityRatio)-2
+ position.x + 1,
+ position.y + 1,
+ size.x - 2,
+ size.y - 2
);
}
@@ -289,21 +300,70 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
// changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
+ if (typeof scale === 'number' && scale !== 1) {
+ // draw tile at a different scale
+ position = position.times(scale);
+ size = size.times(scale);
+ }
+
+ if (translate instanceof $.Point) {
+ // shift tile position slightly
+ position = position.plus(translate);
+ }
+
context.drawImage(
rendered.canvas,
0,
0,
rendered.canvas.width,
rendered.canvas.height,
- position.x * $.pixelDensityRatio,
- position.y * $.pixelDensityRatio,
- size.x * $.pixelDensityRatio,
- size.y * $.pixelDensityRatio
+ position.x,
+ position.y,
+ size.x,
+ size.y
);
context.restore();
},
+ /**
+ * Get the ratio between current and original size.
+ * @function
+ * @return {Float}
+ */
+ getScaleForEdgeSmoothing: function() {
+ if (!this.cacheImageRecord) {
+ $.console.warn(
+ '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
+ this.toString());
+ return 1;
+ }
+
+ var rendered = this.cacheImageRecord.getRenderedContext();
+ return rendered.canvas.width / this.size.times($.pixelDensityRatio).x;
+ },
+
+ /**
+ * Get a translation vector that when applied to the tile position produces integer coordinates.
+ * Needed to avoid swimming and twitching.
+ * @function
+ * @param {Number} [scale=1] - Scale to be applied to position.
+ * @return {OpenSeadragon.Point}
+ */
+ getTranslationForEdgeSmoothing: function(scale) {
+ // The translation vector must have positive values, otherwise the image goes a bit off
+ // the sketch canvas to the top and left and we must use negative coordinates to repaint it
+ // to the main canvas. And FF does not like it. It crashes the viewer.
+ return new $.Point(1, 1).minus(
+ this.position
+ .times($.pixelDensityRatio)
+ .times(scale || 1)
+ .apply(function(x) {
+ return x % 1;
+ })
+ );
+ },
+
/**
* Removes tile from its container.
* @function
diff --git a/src/tiledimage.js b/src/tiledimage.js
index 4bdeb634..3e084ad1 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -64,6 +64,7 @@
* @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.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.
* @param {String} [options.compositeOperation='source-over'] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
@@ -133,20 +134,21 @@ $.TiledImage = function( options ) {
_needsDraw: true, // Does the tiledImage need to update the viewport again?
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
//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,
- placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
- opacity: $.DEFAULT_SETTINGS.opacity,
- compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
+ 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,
+ smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
+ debugMode: $.DEFAULT_SETTINGS.debugMode,
+ crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
+ placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
+ opacity: $.DEFAULT_SETTINGS.opacity
+ compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
}, options );
@@ -356,23 +358,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.
*/
imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight, current ) {
- if (imageX instanceof $.Rect) {
+ var rect = imageX;
+ if (rect instanceof $.Rect) {
//they passed a rect instead of individual components
current = imageY;
- pixelWidth = imageX.width;
- pixelHeight = imageX.height;
- imageY = imageX.y;
- imageX = imageX.x;
+ } else {
+ rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
}
- var coordA = this.imageToViewportCoordinates(imageX, imageY, current);
- var coordB = this._imageToViewportDelta(pixelWidth, pixelHeight, current);
+ var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current);
+ var coordB = this._imageToViewportDelta(rect.width, rect.height, current);
return new $.Rect(
coordA.x,
coordA.y,
coordB.x,
- coordB.y
+ coordB.y,
+ rect.degrees
);
},
@@ -388,23 +390,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @return {OpenSeadragon.Rect} A rect representing the coordinates in the image.
*/
viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) {
+ var rect = viewerX;
if (viewerX instanceof $.Rect) {
//they passed a rect instead of individual components
current = viewerY;
- pointWidth = viewerX.width;
- pointHeight = viewerX.height;
- viewerY = viewerX.y;
- viewerX = viewerX.x;
+ } else {
+ rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
}
- var coordA = this.viewportToImageCoordinates(viewerX, viewerY, current);
- var coordB = this._viewportToImageDelta(pointWidth, pointHeight, current);
+ var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current);
+ var coordB = this._viewportToImageDelta(rect.width, rect.height, current);
return new $.Rect(
coordA.x,
coordA.y,
coordB.x,
- coordB.y
+ coordB.y,
+ rect.degrees
);
},
@@ -666,7 +668,7 @@ function updateViewport( tiledImage ) {
haveDrawn = false,
currentTime = $.now(),
viewportBounds = tiledImage.viewport.getBoundsWithMargins( true ),
- zeroRatioC = tiledImage.viewport.deltaPixelsFromPoints(
+ zeroRatioC = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio( 0 ),
true
).x * tiledImage._scaleSpring.current.value,
@@ -747,7 +749,7 @@ function updateViewport( tiledImage ) {
drawLevel = false;
//Avoid calculations for draw if we have already drawn this
- renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPoints(
+ renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio( level ),
true
).x * tiledImage._scaleSpring.current.value;
@@ -761,12 +763,12 @@ function updateViewport( tiledImage ) {
}
//Perform calculations for draw if we haven't drawn this
- renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPoints(
+ renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio( level ),
false
).x * tiledImage._scaleSpring.current.value;
- zeroRatioT = tiledImage.viewport.deltaPixelsFromPoints(
+ zeroRatioT = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio(
Math.max(
tiledImage.source.getClosestLevel( tiledImage.viewport.containerSize ) - 1,
@@ -811,7 +813,7 @@ function updateViewport( tiledImage ) {
drawTiles( tiledImage, tiledImage.lastDrawn );
// Load the new 'best' tile
- if ( best ) {
+ if (best && !best.context2D) {
loadTile( tiledImage, best, currentTime );
}
@@ -956,10 +958,14 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
);
if (!tile.loaded) {
- var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
- if (imageRecord) {
- var image = imageRecord.getImage();
- setTileLoaded(tiledImage, tile, image);
+ if (tile.context2D) {
+ setTileLoaded(tiledImage, tile);
+ } else {
+ var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
+ if (imageRecord) {
+ var image = imageRecord.getImage();
+ setTileLoaded(tiledImage, tile, image);
+ }
}
}
@@ -992,6 +998,7 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds,
exists,
url,
+ context2D,
tile;
if ( !tilesMatrix[ level ] ) {
@@ -1007,6 +1014,8 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds = tileSource.getTileBounds( level, xMod, yMod );
exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod );
+ context2D = tileSource.getContext2D ?
+ tileSource.getContext2D(level, xMod, yMod) : undefined;
bounds.x += ( x - xMod ) / numTiles.x;
bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
@@ -1017,7 +1026,8 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
y,
bounds,
exists,
- url
+ url,
+ context2D
);
}
@@ -1056,12 +1066,12 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
* @property {string} message - The error message.
*/
tiledImage.viewer.raiseEvent("tile-load-failed", {tile: tile, tiledImage: tiledImage, time: time, message: errorMsg});
- if( !tiledImage.debugMode ){
- tile.loading = false;
- tile.exists = false;
- return;
- }
- } else if ( time < tiledImage.lastResetTime ) {
+ tile.loading = false;
+ tile.exists = false;
+ return;
+ }
+
+ if ( time < tiledImage.lastResetTime ) {
$.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url );
tile.loading = false;
return;
@@ -1096,12 +1106,14 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
if (increment === 0) {
tile.loading = false;
tile.loaded = true;
- tiledImage._tileCache.cacheTile({
- image: image,
- tile: tile,
- cutoff: cutoff,
- tiledImage: tiledImage
- });
+ if (!tile.context2D) {
+ tiledImage._tileCache.cacheTile({
+ image: image,
+ tile: tile,
+ cutoff: cutoff,
+ tiledImage: tiledImage
+ });
+ }
tiledImage._needsDraw = true;
}
}
@@ -1144,10 +1156,10 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility,
boundsSize.x *= tiledImage._scaleSpring.current.value;
boundsSize.y *= tiledImage._scaleSpring.current.value;
- var positionC = viewport.pixelFromPoint( boundsTL, true ),
- positionT = viewport.pixelFromPoint( boundsTL, false ),
- sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ),
- sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ),
+ var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
+ positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
+ sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
+ sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
tileCenter = positionT.plus( sizeT.divide( 2 ) ),
tileDistance = viewportCenter.distanceTo( tileCenter );
@@ -1311,24 +1323,47 @@ function compareTiles( previousBest, tile ) {
function drawTiles( tiledImage, lastDrawn ) {
var i,
- tile;
+ tile = lastDrawn[0];
if ( tiledImage.opacity <= 0 ) {
drawDebugInfo( tiledImage, lastDrawn );
return;
}
var useSketch = tiledImage.opacity < 1 || tiledImage.compositeOperation !== 'source-over';
+ var sketchScale;
+ var sketchTranslate;
+
+ var zoom = tiledImage.viewport.getZoom(true);
+ var imageZoom = tiledImage.viewportToImageZoom(zoom);
+ if ( imageZoom > tiledImage.smoothTileEdgesMinZoom && tile) {
+ // When zoomed in a lot (>100%) the tile edges are visible.
+ // So we have to composite them at ~100% and scale them up together.
+ useSketch = true;
+ sketchScale = tile.getScaleForEdgeSmoothing();
+ sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale);
+ }
+>>>>>>> a0a44dbeb5e3030e0acecf108efc19dbd53aaec2
if ( useSketch ) {
tiledImage._drawer._clear( true );
}
+ if (tiledImage.viewport.degrees !== 0) {
+ tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, useSketch);
+ }
+
var usedClip = false;
if ( tiledImage._clip ) {
tiledImage._drawer.saveContext(useSketch);
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
+ if (sketchScale) {
+ clipRect = clipRect.times(sketchScale);
+ }
+ if (sketchTranslate) {
+ clipRect = clipRect.translate(sketchTranslate);
+ }
tiledImage._drawer.setClip(clipRect, useSketch);
usedClip = true;
@@ -1336,6 +1371,12 @@ function drawTiles( tiledImage, lastDrawn ) {
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
+ if (sketchScale) {
+ placeholderRect = placeholderRect.times(sketchScale);
+ }
+ if (sketchTranslate) {
+ placeholderRect = placeholderRect.translate(sketchTranslate);
+ }
var fillStyle = null;
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
@@ -1350,7 +1391,7 @@ function drawTiles( tiledImage, lastDrawn ) {
for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
tile = lastDrawn[ i ];
- tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch );
+ tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );
tile.beingDrawn = true;
if( tiledImage.viewer ){
@@ -1376,8 +1417,12 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer.restoreContext( useSketch );
}
+ if (tiledImage.viewport.degrees !== 0) {
+ tiledImage._drawer._restoreRotationChanges(useSketch);
+ }
+
if ( useSketch ) {
- tiledImage._drawer.blendSketch( tiledImage.opacity, tiledImage.compositeOperation );
+ tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale, sketchTranslate, tiledImage.compositeOperation );
}
drawDebugInfo( tiledImage, lastDrawn );
}
diff --git a/src/viewer.js b/src/viewer.js
index 20748429..16d5cfa1 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -234,7 +234,7 @@ $.Viewer = function( options ) {
style.left = "0px";
}(this.canvas.style));
$.setElementTouchActionNone( this.canvas );
- this.canvas.tabIndex = options.tabIndex || 0;
+ this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex);
//the container is created through applying the ControlDock constructor above
this.container.className = "openseadragon-container";
@@ -1292,19 +1292,21 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
}
}
+ if ($.isArray(options.tileSource)) {
+ setTimeout(function() {
+ raiseAddItemFailed({
+ message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.",
+ source: options.tileSource,
+ options: options
+ });
+ });
+ return;
+ }
+
this._loadQueue.push(myQueueItem);
getTileSourceImplementation( this, options.tileSource, function( tileSource ) {
- if ( tileSource instanceof Array ) {
- raiseAddItemFailed({
- message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.",
- source: tileSource,
- options: options
- });
- return;
- }
-
myQueueItem.tileSource = tileSource;
// add everybody at the front of the queue that's ready to go
@@ -1349,6 +1351,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
blendTime: _this.blendTime,
alwaysBlend: _this.alwaysBlend,
minPixelRatio: _this.minPixelRatio,
+ smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
crossOriginPolicy: _this.crossOriginPolicy,
debugMode: _this.debugMode
});
@@ -2047,12 +2050,30 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
}
}
+ function waitUntilReady(tileSource, originalTileSource) {
+ if (tileSource.ready) {
+ successCallback(tileSource);
+ } else {
+ tileSource.addHandler('ready', function () {
+ successCallback(tileSource);
+ });
+ tileSource.addHandler('open-failed', function (event) {
+ failCallback({
+ message: event.message,
+ source: originalTileSource
+ });
+ });
+ }
+ }
+
setTimeout( function() {
if ( $.type( tileSource ) == 'string' ) {
//If its still a string it means it must be a url at this point
tileSource = new $.TileSource({
url: tileSource,
+ crossOriginPolicy: viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials,
+ useCanvas: viewer.useCanvas,
success: function( event ) {
successCallback( event.tileSource );
}
@@ -2061,10 +2082,16 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
failCallback( event );
} );
- } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ) {
+ } else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
+ if (!tileSource.crossOriginPolicy && viewer.crossOriginPolicy) {
+ tileSource.crossOriginPolicy = viewer.crossOriginPolicy;
+ }
if (tileSource.ajaxWithCredentials === undefined) {
tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
}
+ if (tileSource.useCanvas === undefined) {
+ tileSource.useCanvas = viewer.useCanvas;
+ }
if ( $.isFunction( tileSource.getTileUrl ) ) {
//Custom tile source
@@ -2082,14 +2109,13 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
return;
}
var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] );
- var readySource = new $TileSource( options );
- successCallback( readySource );
+ waitUntilReady(new $TileSource(options), tileSource);
}
} else {
//can assume it's already a tile source implementation
- successCallback( tileSource );
+ waitUntilReady(tileSource, tileSource);
}
- }, 1 );
+ });
}
function getOverlayObject( viewer, overlay ) {
diff --git a/src/viewport.js b/src/viewport.js
index c619198d..63493371 100644
--- a/src/viewport.js
+++ b/src/viewport.js
@@ -175,12 +175,12 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
$.console.assert(bounds.width > 0, "[Viewport.setHomeBounds] bounds.width must be greater than 0");
$.console.assert(bounds.height > 0, "[Viewport.setHomeBounds] bounds.height must be greater than 0");
- this.homeBounds = bounds.clone();
+ this.homeBounds = bounds.clone().rotate(this.degrees).getBoundingBox();
this.contentSize = this.homeBounds.getSize().times(contentFactor);
this.contentAspectX = this.contentSize.x / this.contentSize.y;
this.contentAspectY = this.contentSize.y / this.contentSize.x;
- if( this.viewer ){
+ if (this.viewer) {
/**
* Raised when the viewer's content size or home bounds are reset
* (see {@link OpenSeadragon.Viewport#resetContentSize},
@@ -195,7 +195,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @property {Number} contentFactor
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
- this.viewer.raiseEvent( 'reset-size', {
+ this.viewer.raiseEvent('reset-size', {
contentSize: this.contentSize.clone(),
contentFactor: contentFactor,
homeBounds: this.homeBounds.clone()
@@ -704,7 +704,6 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.centerSpringX.target.value,
this.centerSpringY.target.value
);
- delta = delta.rotate( -this.degrees, new $.Point( 0, 0 ) );
return this.panTo( center.plus( delta ), immediately );
},
@@ -750,14 +749,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @return {OpenSeadragon.Viewport} Chainable.
* @fires OpenSeadragon.Viewer.event:zoom
*/
- zoomBy: function( factor, refPoint, immediately ) {
- if( refPoint instanceof $.Point && !isNaN( refPoint.x ) && !isNaN( refPoint.y ) ) {
- refPoint = refPoint.rotate(
- -this.degrees,
- new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value )
- );
- }
- return this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately );
+ zoomBy: function(factor, refPoint, immediately) {
+ return this.zoomTo(
+ this.zoomSpring.target.value * factor, refPoint, immediately);
},
/**
@@ -807,13 +801,19 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function
* @return {OpenSeadragon.Viewport} Chainable.
*/
- setRotation: function( degrees ) {
- if( !( this.viewer && this.viewer.drawer.canRotate() ) ) {
+ setRotation: function(degrees) {
+ if (!this.viewer || !this.viewer.drawer.canRotate()) {
return this;
}
- degrees = ( degrees + 360 ) % 360;
+ degrees = degrees % 360;
+ if (degrees < 0) {
+ degrees += 360;
+ }
this.degrees = degrees;
+ this.setHomeBounds(
+ this.viewer.world.getHomeBounds(),
+ this.viewer.world.getContentFactor());
this.viewer.forceRedraw();
/**
@@ -826,10 +826,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @property {Number} degrees - The number of degrees the rotation was set to.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
- if (this.viewer !== null)
- {
- this.viewer.raiseEvent('rotate', {"degrees": degrees});
- }
+ this.viewer.raiseEvent('rotate', {"degrees": degrees});
return this;
},
@@ -933,40 +930,89 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
return changed;
},
-
/**
- * Convert a delta (translation vector) from pixels coordinates to viewport coordinates
- * @function
- * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
+ * Convert a delta (translation vector) from viewport coordinates to pixels
+ * coordinates. This method does not take rotation into account.
+ * Consider using deltaPixelsFromPoints if you need to account for rotation.
+ * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
*/
- deltaPixelsFromPoints: function( deltaPoints, current ) {
+ deltaPixelsFromPointsNoRotate: function(deltaPoints, current) {
return deltaPoints.times(
- this._containerInnerSize.x * this.getZoom( current )
+ this._containerInnerSize.x * this.getZoom(current)
);
},
/**
- * Convert a delta (translation vector) from viewport coordinates to pixels coordinates.
- * @function
- * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
+ * Convert a delta (translation vector) from viewport coordinates to pixels
+ * coordinates.
+ * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
*/
- deltaPointsFromPixels: function( deltaPixels, current ) {
+ deltaPixelsFromPoints: function(deltaPoints, current) {
+ return this.deltaPixelsFromPointsNoRotate(
+ deltaPoints.rotate(this.getRotation()),
+ current);
+ },
+
+ /**
+ * Convert a delta (translation vector) from pixels coordinates to viewport
+ * coordinates. This method does not take rotation into account.
+ * Consider using deltaPointsFromPixels if you need to account for rotation.
+ * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
+ */
+ deltaPointsFromPixelsNoRotate: function(deltaPixels, current) {
return deltaPixels.divide(
- this._containerInnerSize.x * this.getZoom( current )
+ this._containerInnerSize.x * this.getZoom(current)
);
},
/**
- * Convert image pixel coordinates to viewport coordinates.
- * @function
- * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
+ * Convert a delta (translation vector) from pixels coordinates to viewport
+ * coordinates.
+ * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
*/
- pixelFromPoint: function( point, current ) {
- return this._pixelFromPoint(point, this.getBounds( current ));
+ deltaPointsFromPixels: function(deltaPixels, current) {
+ return this.deltaPointsFromPixelsNoRotate(deltaPixels, current)
+ .rotate(-this.getRotation());
+ },
+
+ /**
+ * Convert viewport coordinates to pixels coordinates.
+ * This method does not take rotation into account.
+ * Consider using pixelFromPoint if you need to account for rotation.
+ * @param {OpenSeadragon.Point} point the viewport coordinates
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
+ */
+ pixelFromPointNoRotate: function(point, current) {
+ return this._pixelFromPointNoRotate(point, this.getBounds(current));
+ },
+
+ /**
+ * Convert viewport coordinates to pixel coordinates.
+ * @param {OpenSeadragon.Point} point the viewport coordinates
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
+ */
+ pixelFromPoint: function(point, current) {
+ return this._pixelFromPoint(point, this.getBounds(current));
},
// private
- _pixelFromPoint: function( point, bounds ) {
+ _pixelFromPointNoRotate: function(point, bounds) {
return point.minus(
bounds.getTopLeft()
).times(
@@ -976,12 +1022,23 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
);
},
+ // private
+ _pixelFromPoint: function(point, bounds) {
+ return this._pixelFromPointNoRotate(
+ point.rotate(this.getRotation(), this.getCenter(true)),
+ bounds);
+ },
+
/**
- * Convert viewport coordinates to image pixel coordinates.
- * @function
- * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
+ * Convert pixel coordinates to viewport coordinates.
+ * This method does not take rotation into account.
+ * Consider using pointFromPixel if you need to account for rotation.
+ * @param {OpenSeadragon.Point} pixel Pixel coordinates
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
*/
- pointFromPixel: function( pixel, current ) {
+ pointFromPixelNoRotate: function(pixel, current) {
var bounds = this.getBounds( current );
return pixel.minus(
new $.Point(this._margins.left, this._margins.top)
@@ -992,6 +1049,20 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
);
},
+ /**
+ * Convert pixel coordinates to viewport coordinates.
+ * @param {OpenSeadragon.Point} pixel Pixel coordinates
+ * @param {Boolean} [current=false] - Pass true for the current location;
+ * defaults to false (target location).
+ * @returns {OpenSeadragon.Point}
+ */
+ pointFromPixel: function(pixel, current) {
+ return this.pointFromPixelNoRotate(pixel, current).rotate(
+ -this.getRotation(),
+ this.getCenter(true)
+ );
+ },
+
// private
_viewportToImageDelta: function( viewerX, viewerY ) {
var scale = this.homeBounds.width;
@@ -1072,29 +1143,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @param {Number} pixelWidth the width in pixel of the rectangle.
* @param {Number} pixelHeight the height in pixel of the rectangle.
*/
- imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) {
- var coordA,
- coordB,
- rect;
- if( arguments.length == 1 ) {
- //they passed a rectangle instead of individual components
- rect = imageX;
- return this.imageToViewportRectangle(
- rect.x, rect.y, rect.width, rect.height
- );
+ imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {
+ var rect = imageX;
+ if (!(rect instanceof $.Rect)) {
+ //they passed individual components instead of a rectangle
+ rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
}
- coordA = this.imageToViewportCoordinates(
- imageX, imageY
- );
- coordB = this._imageToViewportDelta(
- pixelWidth, pixelHeight
- );
+ var coordA = this.imageToViewportCoordinates(rect.x, rect.y);
+ var coordB = this._imageToViewportDelta(rect.width, rect.height);
return new $.Rect(
coordA.x,
coordA.y,
coordB.x,
- coordB.y
+ coordB.y,
+ rect.degrees
);
},
@@ -1113,25 +1176,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @param {Number} pointWidth the width of the rectangle in viewport coordinate system.
* @param {Number} pointHeight the height of the rectangle in viewport coordinate system.
*/
- viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) {
- var coordA,
- coordB,
- rect;
- if ( arguments.length == 1 ) {
- //they passed a rectangle instead of individual components
- rect = viewerX;
- return this.viewportToImageRectangle(
- rect.x, rect.y, rect.width, rect.height
- );
+ viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) {
+ var rect = viewerX;
+ if (!(rect instanceof $.Rect)) {
+ //they passed individual components instead of a rectangle
+ rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
}
- coordA = this.viewportToImageCoordinates( viewerX, viewerY );
- coordB = this._viewportToImageDelta(pointWidth, pointHeight);
+ var coordA = this.viewportToImageCoordinates(rect.x, rect.y);
+ var coordB = this._viewportToImageDelta(rect.width, rect.height);
return new $.Rect(
coordA.x,
coordA.y,
coordB.x,
- coordB.y
+ coordB.y,
+ rect.degrees
);
},
diff --git a/test/coverage.html b/test/coverage.html
index 65cc2818..15d3d490 100644
--- a/test/coverage.html
+++ b/test/coverage.html
@@ -32,6 +32,7 @@
+
@@ -74,6 +75,7 @@
+
diff --git a/test/data/iiif_2_0_sizes/full/1600,/0/default.jpg b/test/data/iiif_2_0_sizes/full/1600,/0/default.jpg
new file mode 100644
index 00000000..9156c9d8
Binary files /dev/null and b/test/data/iiif_2_0_sizes/full/1600,/0/default.jpg differ
diff --git a/test/data/iiif_2_0_sizes/full/3200,/0/default.jpg b/test/data/iiif_2_0_sizes/full/3200,/0/default.jpg
new file mode 100644
index 00000000..7cad3870
Binary files /dev/null and b/test/data/iiif_2_0_sizes/full/3200,/0/default.jpg differ
diff --git a/test/data/iiif_2_0_sizes/full/400,/0/default.jpg b/test/data/iiif_2_0_sizes/full/400,/0/default.jpg
new file mode 100644
index 00000000..6d2433df
Binary files /dev/null and b/test/data/iiif_2_0_sizes/full/400,/0/default.jpg differ
diff --git a/test/data/iiif_2_0_sizes/full/6976,/0/default.jpg b/test/data/iiif_2_0_sizes/full/6976,/0/default.jpg
new file mode 100644
index 00000000..8e627bbc
Binary files /dev/null and b/test/data/iiif_2_0_sizes/full/6976,/0/default.jpg differ
diff --git a/test/data/iiif_2_0_sizes/full/800,/0/default.jpg b/test/data/iiif_2_0_sizes/full/800,/0/default.jpg
new file mode 100644
index 00000000..b574b541
Binary files /dev/null and b/test/data/iiif_2_0_sizes/full/800,/0/default.jpg differ
diff --git a/test/data/iiif_2_0_sizes/info.json b/test/data/iiif_2_0_sizes/info.json
new file mode 100644
index 00000000..c78e059b
--- /dev/null
+++ b/test/data/iiif_2_0_sizes/info.json
@@ -0,0 +1,15 @@
+{
+ "@context": "http://iiif.io/api/image/2/context.json",
+ "@id": "http://localhost:8000/test/data/iiif_2_0_sizes",
+ "protocol": "http://iiif.io/api/image",
+ "width": 6976,
+ "height": 5074,
+ "profile": ["http://iiif.io/api/image/2/level0.json"],
+ "sizes" : [
+ {"width" : 400, "height" : 291},
+ {"width" : 800, "height" : 582},
+ {"width" : 1600, "height" : 1164},
+ {"width" : 3200, "height": 2328},
+ {"width" : 6976, "height": 5074}
+ ]
+}
diff --git a/test/demo/basic.html b/test/demo/basic.html
index 19572b86..e238e5e1 100644
--- a/test/demo/basic.html
+++ b/test/demo/basic.html
@@ -6,10 +6,10 @@
diff --git a/test/demo/coordinates.html b/test/demo/coordinates.html
index 188e87ee..3cf836dd 100644
--- a/test/demo/coordinates.html
+++ b/test/demo/coordinates.html
@@ -24,38 +24,55 @@
|
Window (pixel) |
Container (pixel) |
- Image 1 - top left (pixel) |
- Image 2 - bottom right (pixel) |
Viewport (point) |
+ Big Image (pixel) |
+ Small Image (pixel) |
Cursor position |
- |
- |
- |
- |
- |
+ |
+ |
+ |
+ |
+ |
+
+
+ Big Image top left position |
+ |
+ |
+ |
+ |
+ |
+
+
+ Small Image top left position |
+ |
+ |
+ |
+ |
+ |
Zoom |
- |
- |
+ |
|
|
- |
+