Merge pull request #1903 from ali1234/flipmode

Implement per-image flipping
This commit is contained in:
Ian Gilman 2021-03-26 13:40:20 -07:00 committed by GitHub
commit e7d4f87ca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 206 additions and 17 deletions

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

@ -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 {
// This tile is outside of the viewport, no need to draw it flippedX = x;
continue; }
}
if (drawArea.intersection(tiledImage.getTileBounds(level, flippedX, y)) === null) {
// This tile is outside of the viewport, no need to draw it
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 (xMod === numTiles.x - 1) { if (tiledImage.getFlip()) {
tile.isRightMost = true; if (xMod === 0) {
tile.isRightMost = true;
}
} else {
if (xMod === numTiles.x - 1) {
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

@ -1301,6 +1301,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 +1464,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,

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>