Adds flip/mirror slides feature.

This commit adds full support for a new OpenSeaDragon(OSD) feature - Mirror/Flip.
In order to fully support this new feature, the following OSD objects had to be modified:
        - drawer.js:
        - navigator
        - openseadragon
        - strings
        - tiledImage
	- viewer

Additionally, a new flip button was created (similar to the existing ones).

Flip Logic
        Whenever the state is flip button is pressed, flip state is toogled, inverting all the controls and displays (the rotation direction is inverted as well).
        This means that all viewer coordinates (including user inputs) must me inverted too.

Summary of modifications
        - drawer.js: modified _offsetForRotation to invert rotation angle on flipped state. Added a _flip method to scale/mirror canvas context.
        - navigator.js: adds full flip support and inverts nagivator inputs.
        - openseadragon.js: new buttons, flip state variable and showFlipControl variable.
        - strings.js: flip tool help tips.
        - tiledImage.js: flips the actual drawing canvas.
	- viewer.js: Added keyboardshortcuts to rotate 90degrees (r/R) and flip image (f/F). flip button state is stored here and flip order is set.

The flipped state is stored on viewer object.
This commit is contained in:
Nelson Campos 2018-04-04 11:31:18 +01:00
parent e5355abafe
commit 71fd747051
11 changed files with 205 additions and 22 deletions

BIN
images/flip_grouphover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
images/flip_hover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
images/flip_pressed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
images/flip_rest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -500,6 +500,10 @@ $.Drawer.prototype = {
if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation({degrees: this.viewport.degrees});
} else{
if(this.viewer.flipped) {
this._flip({});
}
}
if (tiledImage.getRotation(true) % 360 !== 0) {
this._offsetForRotation({
@ -620,10 +624,29 @@ $.Drawer.prototype = {
context.save();
context.translate(point.x, point.y);
if(this.viewer.flipped){
context.rotate(Math.PI / 180 * -options.degrees);
context.scale(-1, 1);
}
else{
context.rotate(Math.PI / 180 * options.degrees);
}
context.translate(-point.x, -point.y);
},
// private
_flip: function(options) {
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
var context = this._getContext(options.useSketch);
context.save();
context.translate(point.x, 0);
context.scale(-1, 1);
context.translate(-point.x, 0);
},
// private
_restoreRotationChanges: function(useSketch) {
var context = this._getContext(useSketch);

View File

@ -208,12 +208,14 @@ $.Navigator = function( options ){
var degrees = options.viewer.viewport ?
options.viewer.viewport.getRotation() :
options.viewer.degrees || 0;
rotate(degrees);
options.viewer.addHandler("rotate", function (args) {
rotate(args.degrees);
});
}
// Remove the base class' (Viewer's) innerTracker and replace it with our own
this.innerTracker.destroy();
this.innerTracker = new $.MouseTracker({
@ -271,6 +273,50 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
}
}
},
/**
/* Flip navigator element
*/
toogleFlip: function() {
this.flipped = !this.flipped;
if (this.viewer.flipped){
this.displayRegion.style.webkitTransform = "scale(-1,1)";
this.displayRegion.style.mozTransform = "scale(-1,1)";
this.displayRegion.style.msTransform = "scale(-1,1)";
this.displayRegion.style.oTransform = "scale(-1,1)";
this.displayRegion.style.transform = "scale(-1,1)";
this.canvas.style.webkitTransform = "scale(-1,1)";
this.canvas.style.mozTransform = "scale(-1,1)";
this.canvas.style.msTransform = "scale(-1,1)";
this.canvas.style.oTransform = "scale(-1,1)";
this.canvas.style.transform = "scale(-1,1)";
this.element.style.webkitTransform = "scale(-1,1)";
this.element.style.mozTransform = "scale(-1,1)";
this.element.style.msTransform = "scale(-1,1)";
this.element.style.oTransform = "scale(-1,1)";
this.element.style.transform = "scale(-1,1)";
} else{
this.displayRegion.style.webkitTransform = "scale(1,1)";
this.displayRegion.style.mozTransform = "scale(1,1)";
this.displayRegion.style.msTransform = "scale(1,1)";
this.displayRegion.style.oTransform = "scale(1,1)";
this.displayRegion.style.transform = "scale(1,1)";
this.canvas.style.webkitTransform = "scale(1,1)";
this.canvas.style.mozTransform = "scale(1,1)";
this.canvas.style.msTransform = "scale(1,1)";
this.canvas.style.oTransform = "scale(1,1)";
this.canvas.style.transform = "scale(1,1)";
this.element.style.webkitTransform = "scale(1,1)";
this.element.style.mozTransform = "scale(1,1)";
this.element.style.msTransform = "scale(1,1)";
this.element.style.oTransform = "scale(1,1)";
this.element.style.transform = "scale(1,1)";
}
this.viewport.viewer.forceRedraw();
},
/**
* Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs.
@ -406,6 +452,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
*/
function onCanvasClick( event ) {
if ( event.quick && this.viewer.viewport ) {
if(this.viewer.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
this.viewer.viewport.panTo(this.viewport.pointFromPixel(event.position));
this.viewer.viewport.applyConstraints();
}
@ -424,6 +473,11 @@ function onCanvasDrag( event ) {
if( !this.panVertical ){
event.delta.y = 0;
}
if(this.viewer.flipped){
event.delta.x = -event.delta.x;
}
this.viewer.viewport.panBy(
this.viewport.deltaPointsFromPixels(
event.delta

View File

@ -478,6 +478,10 @@
* Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
* this setting when set to false.
*
* @property {Boolean} [showFlipControl=false]
* If true then the flip controls will be displayed as part of the
* standard controls.
*
* @property {Boolean} [showSequenceControl=true]
* If sequenceMode is true, then provide buttons for navigating forward and
* backward through the images.
@ -688,6 +692,12 @@
* @property {String} rotateright.HOVER
* @property {String} rotateright.DOWN
*
* @property {Object} flip - Images for the flip button.
* @property {String} flip.REST
* @property {String} flip.GROUP
* @property {String} flip.HOVER
* @property {String} flip.DOWN
*
* @property {Object} previous - Images for the previous button.
* @property {String} previous.REST
* @property {String} previous.GROUP
@ -1120,6 +1130,7 @@ function OpenSeadragon( options ){
showHomeControl: true, //HOME
showFullPageControl: true, //FULL
showRotationControl: false, //ROTATION
showFlipControl: false, //FLIP
controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
@ -1141,6 +1152,9 @@ function OpenSeadragon( options ){
// INITIAL ROTATION
degrees: 0,
// INITIAL FLIP STATE
flipped: false,
// APPEARANCE
opacity: 1,
preload: false,
@ -1209,6 +1223,12 @@ function OpenSeadragon( options ){
HOVER: 'rotateright_hover.png',
DOWN: 'rotateright_pressed.png'
},
flip: { // Flip icon by Yaroslav Samoylov from the Noun Project
REST: 'flip_rest.png',
GROUP: 'flip_grouphover.png',
HOVER: 'flip_hover.png',
DOWN: 'flip_pressed.png'
},
previous: {
REST: 'previous_rest.png',
GROUP: 'previous_grouphover.png',

View File

@ -57,7 +57,8 @@ var I18N = {
NextPage: "Next page",
PreviousPage: "Previous page",
RotateLeft: "Rotate left",
RotateRight: "Rotate right"
RotateRight: "Rotate right",
Flip: "Flip Horizontally"
}
};

View File

@ -1886,6 +1886,10 @@ function drawTiles( tiledImage, lastDrawn ) {
degrees: tiledImage.viewport.degrees,
useSketch: useSketch
});
} else {
if(tiledImage._drawer.viewer.flipped) {
tiledImage._drawer._flip({});
}
}
if (tiledImage.getRotation(true) % 360 !== 0) {
tiledImage._drawer._offsetForRotation({
@ -1969,6 +1973,10 @@ function drawTiles( tiledImage, lastDrawn ) {
}
if (tiledImage.viewport.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(useSketch);
} else{
if(tiledImage._drawer.viewer.flipped) {
tiledImage._drawer._flip({});
}
}
}

View File

@ -1252,6 +1252,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] Initial flip/mirror state
* @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.
@ -1414,6 +1415,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,
@ -1674,6 +1676,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
onFullScreenHandler = $.delegate( this, onFullScreen ),
onRotateLeftHandler = $.delegate( this, onRotateLeft ),
onRotateRightHandler = $.delegate( this, onRotateRight ),
onFlipHandler = $.delegate( this, onFlip),
onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ),
navImages = this.navImages,
@ -1685,7 +1688,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if( this.zoomInButton || this.zoomOutButton ||
this.homeButton || this.fullPageButton ||
this.rotateLeftButton || this.rotateRightButton ) {
this.rotateLeftButton || this.rotateRightButton ||
this.flipButton ) {
//if we are binding to custom buttons then layout and
//grouping is the responsibility of the page author
useGroup = false;
@ -1789,7 +1793,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
onFocus: onFocusHandler,
onBlur: onBlurHandler
}));
}
if ( this.showFlipControl ) {
buttons.push( this.flipButton = new $.Button({
element: this.flipButton ? $.getElement( this.flipButton ) : null,
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.Flip" ),
srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ),
srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ),
srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ),
srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ),
onRelease: onFlipHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
}));
}
if ( useGroup ) {
@ -2602,8 +2621,26 @@ function onCanvasKeyPress( event ) {
this.viewport.applyConstraints();
}
return false;
case 114: //r
case 82: //R
if(this.flipped){
this.viewport.setRotation(this.viewport.degrees - 90);
} else{
this.viewport.setRotation(this.viewport.degrees + 90);
}
this.viewport.applyConstraints();
return false;
case 70: //F
case 102: //f
this.flipped = !this.flipped;
if(this.navigator){
this.navigator.toogleFlip();
}
this._forceRedraw = !this._forceRedraw;
this.forceRedraw();
return false;
default:
//console.log( 'navigator keycode %s', event.keyCode );
// console.log( 'navigator keycode %s', event.keyCode );
return true;
}
} else {
@ -2620,6 +2657,9 @@ function onCanvasClick( event ) {
if ( !haveKeyboardFocus ) {
this.canvas.focus();
}
if(this.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
var canvasClickEventArgs = {
tracker: event.eventSource,
@ -2739,6 +2779,9 @@ function onCanvasDrag( event ) {
if( !this.panVertical ){
event.delta.y = 0;
}
if(this.flipped){
event.delta.x = -event.delta.x;
}
if( this.constrainDuringPan ){
var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
@ -3064,6 +3107,10 @@ function onCanvasScroll( event ) {
if (deltaScrollTime > this.minScrollDeltaTime) {
this._lastScrollTime = thisScrollTime;
if(this.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
if ( !event.preventDefaultAction && this.viewport ) {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if ( gestureSettings.scrollToZoom ) {
@ -3429,12 +3476,21 @@ function onFullScreen() {
function onRotateLeft() {
if ( this.viewport ) {
var currRotation = this.viewport.getRotation();
if ( this.flipped ){
if (currRotation === 270) {
currRotation = 0;
}
else {
currRotation += 90;
}
} else {
if (currRotation === 0) {
currRotation = 270;
}
else {
currRotation -= 90;
}
}
this.viewport.setRotation(currRotation);
}
}
@ -3445,16 +3501,38 @@ function onRotateLeft() {
function onRotateRight() {
if ( this.viewport ) {
var currRotation = this.viewport.getRotation();
if ( this.flipped ){
if (currRotation === 0) {
currRotation = 270;
}
else {
currRotation -= 90;
}
} else {
if (currRotation === 270) {
currRotation = 0;
}
else {
currRotation += 90;
}
}
this.viewport.setRotation(currRotation);
}
}
/**
* Note: The current rotation feature is limited to 90 degree turns.
*/
function onFlip() {
this.flipped = !this.flipped;
if(this.navigator){
this.navigator.toogleFlip();
}
this._forceRedraw = !this._forceRedraw;
this.forceRedraw();
}
function onPrevious(){
var previous = this._sequenceIndex - 1;

View File

@ -886,7 +886,6 @@ $.Viewport.prototype = {
this.viewer.world.getHomeBounds(),
this.viewer.world.getContentFactor());
this.viewer.forceRedraw();
/**
* Raised when rotation has been changed.
*