Merge branch 'master' into ms-reference-strip

* master: (27 commits)
  Changelog for #1968
  Fixing issue where the ajaxHeaders were not being set for image requests
  Changelog for #1865
  Changelog for #1937
  Changelog for #1903
  refactor: moved methods that belongs together closer
  Make setFlip() update the navigator
  refactor: use pixelDensityRatio in getPixelRatio()
  fix: removes resize event on destroy
  docs: fixed typo and corrected the comment
  fix: made updatePixelDensityRatio private
  Improve the flipping example
  Tidy up the tile/image flip check
  Force reload tiles when the tile's flip doesn't match the image
  Add a basic setFlip method to TiledImage
  Add flipping example
  Correctly set the rightmost tile property when flipped
  Render the flipped columns in reverse order
  Store the flipped state in each tile and render it as such
  Introduce getTileBounds method for tiledImage
  ...
This commit is contained in:
Mark Salsbery 2021-05-02 10:08:55 -07:00
commit e69f8d079e
9 changed files with 260 additions and 26 deletions

View File

@ -44,6 +44,10 @@ OPENSEADRAGON CHANGELOG
* Fixed test "Events: Viewer: preventDefaultAction in dblClickHandler". Fixes #1372 (#1960 @msalsbery) * Fixed test "Events: Viewer: preventDefaultAction in dblClickHandler". Fixes #1372 (#1960 @msalsbery)
* ReferenceStrip: Fixed issue where its element was being removed from its parent element twice on destroy, causing an exception (#1958 @msalsbery) * ReferenceStrip: Fixed issue where its element was being removed from its parent element twice on destroy, causing an exception (#1958 @msalsbery)
* ReferenceStrip: Made its element focusable for keyboard navigation (#1958 @msalsbery) * ReferenceStrip: Made its element focusable for keyboard navigation (#1958 @msalsbery)
* You can now flip individual images (not just the whole viewport) (#1903 @ali1234)
* Accessibility: we now take the browser's zoom into account when choosing what detail level to draw (#1937 @ronnymikalsen)
* Fixed a bug causing overlays to disappear in Sequence Mode (#1865 @gunmiosb)
* Fixed a bug where the ajaxHeaders provided per-image were not being used for image requests (#1968 @maxshuty)
2.4.2: 2.4.2:

View File

@ -452,6 +452,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
myItem.setWidth(bounds.width, immediately); myItem.setWidth(bounds.width, immediately);
myItem.setRotation(theirItem.getRotation(), immediately); myItem.setRotation(theirItem.getRotation(), immediately);
myItem.setClip(theirItem.getClip()); myItem.setClip(theirItem.getClip());
myItem.setFlip(theirItem.getFlip());
}, },
// private // private

View File

@ -986,7 +986,7 @@ function OpenSeadragon( options ){
* @member {Number} pixelDensityRatio * @member {Number} pixelDensityRatio
* @memberof OpenSeadragon * @memberof OpenSeadragon
*/ */
$.pixelDensityRatio = (function () { $.getCurrentPixelDensityRatio = function() {
if ( $.supportsCanvas ) { if ( $.supportsCanvas ) {
var context = document.createElement('canvas').getContext('2d'); var context = document.createElement('canvas').getContext('2d');
var devicePixelRatio = window.devicePixelRatio || 1; var devicePixelRatio = window.devicePixelRatio || 1;
@ -999,7 +999,13 @@ function OpenSeadragon( options ){
} else { } else {
return 1; return 1;
} }
}()); };
/**
* @member {Number} pixelDensityRatio
* @memberof OpenSeadragon
*/
$.pixelDensityRatio = $.getCurrentPixelDensityRatio();
}( OpenSeadragon )); }( OpenSeadragon ));

View File

@ -288,11 +288,8 @@
style[transformProp] = ""; style[transformProp] = "";
} }
} }
if (style.display !== 'none') {
style.display = 'block'; style.display = 'block';
} }
}
}, },
// private // private

View File

@ -176,6 +176,12 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @memberof OpenSeadragon.Tile# * @memberof OpenSeadragon.Tile#
*/ */
this.size = null; this.size = null;
/**
* Whether to flip the tile when rendering.
* @member {Boolean} flipped
* @memberof OpenSeadragon.Tile#
*/
this.flipped = false;
/** /**
* The start time of this tile's blending. * The start time of this tile's blending.
* @member {Number} blendStart * @member {Number} blendStart
@ -296,6 +302,10 @@ $.Tile.prototype = {
this.style.height = this.size.y + "px"; this.style.height = this.size.y + "px";
this.style.width = this.size.x + "px"; this.style.width = this.size.x + "px";
if (this.flipped) {
this.style.transform = "scaleX(-1)";
}
$.setElementOpacity( this.element, this.opacity ); $.setElementOpacity( this.element, this.opacity );
}, },
@ -376,13 +386,17 @@ $.Tile.prototype = {
sourceHeight = rendered.canvas.height; sourceHeight = rendered.canvas.height;
} }
context.translate(position.x + size.x / 2, 0);
if (this.flipped) {
context.scale(-1, 1);
}
context.drawImage( context.drawImage(
rendered.canvas, rendered.canvas,
0, 0,
0, 0,
sourceWidth, sourceWidth,
sourceHeight, sourceHeight,
position.x, -size.x / 2,
position.y, position.y,
size.x, size.x,
size.y size.y

View File

@ -392,6 +392,26 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return bounds.rotate(this.getRotation(current), this._getRotationPoint(current)); return bounds.rotate(this.getRotation(current), this._getRotationPoint(current));
}, },
/**
* @function
* @param {Number} level
* @param {Number} x
* @param {Number} y
* @returns {OpenSeadragon.Rect} Where this tile fits (in normalized coordinates).
*/
getTileBounds: function( level, x, y ) {
var numTiles = this.source.getNumTiles(level);
var xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
var yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
var bounds = this.source.getTileBounds(level, xMod, yMod);
if (this.getFlip()) {
bounds.x = 1 - bounds.x - bounds.width;
}
bounds.x += (x - xMod) / numTiles.x;
bounds.y += (this._worldHeightCurrent / this._worldWidthCurrent) * ((y - yMod) / numTiles.y);
return bounds;
},
/** /**
* @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels. * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels.
*/ */
@ -832,6 +852,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this.raiseEvent('clip-change'); this.raiseEvent('clip-change');
}, },
/**
* @returns {Boolean} Whether the TiledImage should be flipped before rendering.
*/
getFlip: function() {
return !!this.flipped;
},
/**
* @param {Boolean} flip Whether the TiledImage should be flipped before rendering.
* @fires OpenSeadragon.TiledImage.event:bounds-change
*/
setFlip: function(flip) {
this.flipped = !!flip;
this._needsDraw = true;
this._raiseBoundsChange();
},
/** /**
* @returns {Number} The TiledImage's current opacity. * @returns {Number} The TiledImage's current opacity.
*/ */
@ -1255,24 +1292,41 @@ function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
var viewportCenter = tiledImage.viewport.pixelFromPoint( var viewportCenter = tiledImage.viewport.pixelFromPoint(
tiledImage.viewport.getCenter()); tiledImage.viewport.getCenter());
if (tiledImage.getFlip()) {
// The right-most tile can be narrower than the others. When flipped,
// this tile is now on the left. Because it is narrower than the normal
// left-most tile, the subsequent tiles may not be wide enough to completely
// fill the viewport. Fix this by rendering an extra column of tiles. If we
// are not wrapping, make sure we never render more than the number of tiles
// in the image.
bottomRightTile.x += 1;
if (!tiledImage.wrapHorizontal) {
bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1);
}
}
for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) { for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) { for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
// Optimisation disabled with wrapping because getTileBounds does not var flippedX;
// work correctly with x and y outside of the number of tiles if (tiledImage.getFlip()) {
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) { var xMod = ( numberOfTiles.x + ( x % numberOfTiles.x ) ) % numberOfTiles.x;
var tileBounds = tiledImage.source.getTileBounds(level, x, y); flippedX = x + numberOfTiles.x - xMod - xMod - 1;
if (drawArea.intersection(tileBounds) === null) { } else {
flippedX = x;
}
if (drawArea.intersection(tiledImage.getTileBounds(level, flippedX, y)) === null) {
// This tile is outside of the viewport, no need to draw it // This tile is outside of the viewport, no need to draw it
continue; continue;
} }
}
best = updateTile( best = updateTile(
tiledImage, tiledImage,
drawLevel, drawLevel,
haveDrawn, haveDrawn,
x, y, flippedX, y,
level, level,
levelOpacity, levelOpacity,
levelVisibility, levelVisibility,
@ -1447,10 +1501,10 @@ function getTile(
tilesMatrix[ level ][ x ] = {}; tilesMatrix[ level ][ x ] = {};
} }
if ( !tilesMatrix[ level ][ x ][ y ] ) { if ( !tilesMatrix[ level ][ x ][ y ] || !tilesMatrix[ level ][ x ][ y ].flipped !== !tiledImage.flipped ) {
xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
bounds = tileSource.getTileBounds( level, xMod, yMod ); bounds = tiledImage.getTileBounds( level, x, y );
sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true ); sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );
exists = tileSource.tileExists( level, xMod, yMod ); exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod ); url = tileSource.getTileUrl( level, xMod, yMod );
@ -1469,9 +1523,6 @@ function getTile(
context2D = tileSource.getContext2D ? context2D = tileSource.getContext2D ?
tileSource.getContext2D(level, xMod, yMod) : undefined; tileSource.getContext2D(level, xMod, yMod) : undefined;
bounds.x += ( x - xMod ) / numTiles.x;
bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
tile = new $.Tile( tile = new $.Tile(
level, level,
x, x,
@ -1485,14 +1536,22 @@ function getTile(
sourceBounds sourceBounds
); );
if (tiledImage.getFlip()) {
if (xMod === 0) {
tile.isRightMost = true;
}
} else {
if (xMod === numTiles.x - 1) { if (xMod === numTiles.x - 1) {
tile.isRightMost = true; tile.isRightMost = true;
} }
}
if (yMod === numTiles.y - 1) { if (yMod === numTiles.y - 1) {
tile.isBottomMost = true; tile.isBottomMost = true;
} }
tile.flipped = tiledImage.flipped;
tilesMatrix[ level ][ x ][ y ] = tile; tilesMatrix[ level ][ x ][ y ] = tile;
} }

View File

@ -313,8 +313,8 @@ $.TileSource.prototype = {
*/ */
getPixelRatio: function( level ) { getPixelRatio: function( level ) {
var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ), var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ),
rx = 1.0 / imageSizeScaled.x, rx = 1.0 / imageSizeScaled.x * $.pixelDensityRatio,
ry = 1.0 / imageSizeScaled.y; ry = 1.0 / imageSizeScaled.y * $.pixelDensityRatio;
return new $.Point(rx, ry); return new $.Point(rx, ry);
}, },

View File

@ -220,6 +220,7 @@ $.Viewer = function( options ) {
this._updateRequestId = null; this._updateRequestId = null;
this._loadQueue = []; this._loadQueue = [];
this.currentOverlays = []; this.currentOverlays = [];
this._updatePixelDensityRatioBind = null;
this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices
@ -426,6 +427,8 @@ $.Viewer = function( options ) {
} }
} }
this._addUpdatePixelDensityRatioEvent();
//Instantiate a navigator if configured //Instantiate a navigator if configured
if ( this.showNavigator){ if ( this.showNavigator){
this.navigator = new $.Navigator({ this.navigator = new $.Navigator({
@ -748,6 +751,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return; return;
} }
this._removeUpdatePixelDensityRatioEvent();
this.close(); this.close();
this.clearOverlays(); this.clearOverlays();
@ -1301,6 +1306,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks) * @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
* @param {Number} [options.degrees=0] Initial rotation of the tiled image around * @param {Number} [options.degrees=0] Initial rotation of the tiled image around
* its top left corner in degrees. * its top left corner in degrees.
* @param {Boolean} [options.flipped=false] Whether to horizontally flip the image.
* @param {String} [options.compositeOperation] How the image is composited onto other images. * @param {String} [options.compositeOperation] How the image is composited onto other images.
* @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image, * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
* overriding viewer.crossOriginPolicy. * overriding viewer.crossOriginPolicy.
@ -1463,6 +1469,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
opacity: queueItem.options.opacity, opacity: queueItem.options.opacity,
preload: queueItem.options.preload, preload: queueItem.options.preload,
degrees: queueItem.options.degrees, degrees: queueItem.options.degrees,
flipped: queueItem.options.flipped,
compositeOperation: queueItem.options.compositeOperation, compositeOperation: queueItem.options.compositeOperation,
springStiffness: _this.springStiffness, springStiffness: _this.springStiffness,
animationTime: _this.animationTime, animationTime: _this.animationTime,
@ -2266,6 +2273,38 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
} else { } else {
$.console.warn('Attempting to display a reference strip while "sequenceMode" is off.'); $.console.warn('Attempting to display a reference strip while "sequenceMode" is off.');
} }
},
/**
* Adds _updatePixelDensityRatio to the window resize event.
* @private
*/
_addUpdatePixelDensityRatioEvent: function() {
this._updatePixelDensityRatioBind = this._updatePixelDensityRatio.bind(this);
$.addEvent( window, 'resize', this._updatePixelDensityRatioBind );
},
/**
* Removes _updatePixelDensityRatio from the window resize event.
* @private
*/
_removeUpdatePixelDensityRatioEvent: function() {
$.removeEvent( window, 'resize', this._updatePixelDensityRatioBind );
},
/**
* Update pixel density ratio, clears all tiles and triggers updates for
* all items if the ratio has changed.
* @private
*/
_updatePixelDensityRatio: function() {
var previusPixelDensityRatio = $.pixelDensityRatio;
var currentPixelDensityRatio = $.getCurrentPixelDensityRatio();
if (previusPixelDensityRatio !== currentPixelDensityRatio) {
$.pixelDensityRatio = currentPixelDensityRatio;
this.world.resetItems();
this.forceRedraw();
}
} }
}); });
@ -2334,7 +2373,8 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ? crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy, imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials, ajaxWithCredentials: viewer.ajaxWithCredentials,
ajaxHeaders: viewer.ajaxHeaders, ajaxHeaders: imgOptions.ajaxHeaders ?
imgOptions.ajaxHeaders : viewer.ajaxHeaders,
useCanvas: viewer.useCanvas, useCanvas: viewer.useCanvas,
success: function( event ) { success: function( event ) {
successCallback( event.tileSource ); successCallback( event.tileSource );

113
test/demo/flipping.html Normal file
View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Flipping Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
float: left;
}
.options {
margin: 0.5em;
}
.button {
margin: 0.3em;
}
</style>
</head>
<body>
<div>
Simple demo page to show image flipping.
</div>
<div id="contentDiv" class="openseadragon1">
</div>
<div class="options">
First
<div class="button">
<input type="checkbox" id="ffirst" onchange="flip(0, this.checked)">
<label for="ffirst">Flip</label>
</div>
<div class="button">
<input type="checkbox" id="rfirst" onchange="rotate(0, this.checked * 45)">
<label for="rfirst">Rotate</label>
</div>
</div>
<div class="options">
Second
<div class="button">
<input type="checkbox" id="fsecond" onchange="flip(1, this.checked)" checked>
<label for="fsecond">Flip</label>
</div>
<div class="button">
<input type="checkbox" id="rsecond" onchange="rotate(1, this.checked * 45)">
<label for="rsecond">Rotate</label>
</div>
</div>
<div class="options">
Viewport
<div class="button">
<input type="checkbox" id="fview" onchange="flipViewport(this.checked)">
<label for="fview">Flip Viewport</label>
</div>
<div class="button">
<input type="checkbox" id="debug" onchange="debug(this.checked)">
<label for="debug">Debug Mode</label>
</div>
</div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
showNavigator:true,
tileSources: [
{
tileSource: "../data/testpattern.dzi",
x: 0,
y: 0,
flipped: document.getElementById("ffirst").checked,
degrees: document.getElementById("rfirst").checked * 45,
}, {
tileSource: "../data/testpattern.dzi",
x: 1,
y: 0,
flipped: document.getElementById("fsecond").checked,
degrees: document.getElementById("rsecond").checked * 45,
}
]
});
viewer.viewport.setFlip(document.getElementById("fview").checked);
function debug(v) {
viewer.setDebugMode(v);
}
function flip(n, v) {
viewer.world.getItemAt(n).setFlip(v);
}
function rotate(n, v) {
viewer.world.getItemAt(n).setRotation(v);
}
function flipViewport(v) {
viewer.viewport.setFlip(v);
}
</script>
</body>
</html>