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)
* 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)
* 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:

View File

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

View File

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

View File

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

View File

@ -176,6 +176,12 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @memberof OpenSeadragon.Tile#
*/
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.
* @member {Number} blendStart
@ -296,6 +302,10 @@ $.Tile.prototype = {
this.style.height = this.size.y + "px";
this.style.width = this.size.x + "px";
if (this.flipped) {
this.style.transform = "scaleX(-1)";
}
$.setElementOpacity( this.element, this.opacity );
},
@ -376,13 +386,17 @@ $.Tile.prototype = {
sourceHeight = rendered.canvas.height;
}
context.translate(position.x + size.x / 2, 0);
if (this.flipped) {
context.scale(-1, 1);
}
context.drawImage(
rendered.canvas,
0,
0,
sourceWidth,
sourceHeight,
position.x,
-size.x / 2,
position.y,
size.x,
size.y

View File

@ -392,6 +392,26 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
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.
*/
@ -832,6 +852,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
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.
*/
@ -1255,24 +1292,41 @@ function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
var viewportCenter = tiledImage.viewport.pixelFromPoint(
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 y = topLeftTile.y; y <= bottomRightTile.y; y++) {
// Optimisation disabled with wrapping because getTileBounds does not
// work correctly with x and y outside of the number of tiles
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
var tileBounds = tiledImage.source.getTileBounds(level, x, y);
if (drawArea.intersection(tileBounds) === null) {
var flippedX;
if (tiledImage.getFlip()) {
var xMod = ( numberOfTiles.x + ( x % numberOfTiles.x ) ) % numberOfTiles.x;
flippedX = x + numberOfTiles.x - xMod - xMod - 1;
} else {
flippedX = x;
}
if (drawArea.intersection(tiledImage.getTileBounds(level, flippedX, y)) === null) {
// This tile is outside of the viewport, no need to draw it
continue;
}
}
best = updateTile(
tiledImage,
drawLevel,
haveDrawn,
x, y,
flippedX, y,
level,
levelOpacity,
levelVisibility,
@ -1447,10 +1501,10 @@ function getTile(
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;
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 );
exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod );
@ -1469,9 +1523,6 @@ function getTile(
context2D = tileSource.getContext2D ?
tileSource.getContext2D(level, xMod, yMod) : undefined;
bounds.x += ( x - xMod ) / numTiles.x;
bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
tile = new $.Tile(
level,
x,
@ -1485,14 +1536,22 @@ function getTile(
sourceBounds
);
if (tiledImage.getFlip()) {
if (xMod === 0) {
tile.isRightMost = true;
}
} else {
if (xMod === numTiles.x - 1) {
tile.isRightMost = true;
}
}
if (yMod === numTiles.y - 1) {
tile.isBottomMost = true;
}
tile.flipped = tiledImage.flipped;
tilesMatrix[ level ][ x ][ y ] = tile;
}

View File

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

View File

@ -220,6 +220,7 @@ $.Viewer = function( options ) {
this._updateRequestId = null;
this._loadQueue = [];
this.currentOverlays = [];
this._updatePixelDensityRatioBind = null;
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
if ( this.showNavigator){
this.navigator = new $.Navigator({
@ -748,6 +751,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return;
}
this._removeUpdatePixelDensityRatioEvent();
this.close();
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 {Number} [options.degrees=0] Initial rotation of the tiled image around
* 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.crossOriginPolicy] The crossOriginPolicy for this specific image,
* overriding viewer.crossOriginPolicy.
@ -1463,6 +1469,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
opacity: queueItem.options.opacity,
preload: queueItem.options.preload,
degrees: queueItem.options.degrees,
flipped: queueItem.options.flipped,
compositeOperation: queueItem.options.compositeOperation,
springStiffness: _this.springStiffness,
animationTime: _this.animationTime,
@ -2266,6 +2273,38 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
} else {
$.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 ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials,
ajaxHeaders: viewer.ajaxHeaders,
ajaxHeaders: imgOptions.ajaxHeaders ?
imgOptions.ajaxHeaders : viewer.ajaxHeaders,
useCanvas: viewer.useCanvas,
success: function( event ) {
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>