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 ) { if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation({degrees: this.viewport.degrees}); this._offsetForRotation({degrees: this.viewport.degrees});
} else{
if(this.viewer.flipped) {
this._flip({});
}
} }
if (tiledImage.getRotation(true) % 360 !== 0) { if (tiledImage.getRotation(true) % 360 !== 0) {
this._offsetForRotation({ this._offsetForRotation({
@ -620,10 +624,29 @@ $.Drawer.prototype = {
context.save(); context.save();
context.translate(point.x, point.y); 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.rotate(Math.PI / 180 * options.degrees);
}
context.translate(-point.x, -point.y); 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 // private
_restoreRotationChanges: function(useSketch) { _restoreRotationChanges: function(useSketch) {
var context = this._getContext(useSketch); var context = this._getContext(useSketch);

View File

@ -208,12 +208,14 @@ $.Navigator = function( options ){
var degrees = options.viewer.viewport ? var degrees = options.viewer.viewport ?
options.viewer.viewport.getRotation() : options.viewer.viewport.getRotation() :
options.viewer.degrees || 0; options.viewer.degrees || 0;
rotate(degrees); rotate(degrees);
options.viewer.addHandler("rotate", function (args) { options.viewer.addHandler("rotate", function (args) {
rotate(args.degrees); rotate(args.degrees);
}); });
} }
// Remove the base class' (Viewer's) innerTracker and replace it with our own // Remove the base class' (Viewer's) innerTracker and replace it with our own
this.innerTracker.destroy(); this.innerTracker.destroy();
this.innerTracker = new $.MouseTracker({ 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. * 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 ) { function onCanvasClick( event ) {
if ( event.quick && this.viewer.viewport ) { 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.panTo(this.viewport.pointFromPixel(event.position));
this.viewer.viewport.applyConstraints(); this.viewer.viewport.applyConstraints();
} }
@ -424,6 +473,11 @@ function onCanvasDrag( event ) {
if( !this.panVertical ){ if( !this.panVertical ){
event.delta.y = 0; event.delta.y = 0;
} }
if(this.viewer.flipped){
event.delta.x = -event.delta.x;
}
this.viewer.viewport.panBy( this.viewer.viewport.panBy(
this.viewport.deltaPointsFromPixels( this.viewport.deltaPointsFromPixels(
event.delta event.delta

View File

@ -478,6 +478,10 @@
* Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
* this setting when set to false. * 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] * @property {Boolean} [showSequenceControl=true]
* If sequenceMode is true, then provide buttons for navigating forward and * If sequenceMode is true, then provide buttons for navigating forward and
* backward through the images. * backward through the images.
@ -688,6 +692,12 @@
* @property {String} rotateright.HOVER * @property {String} rotateright.HOVER
* @property {String} rotateright.DOWN * @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 {Object} previous - Images for the previous button.
* @property {String} previous.REST * @property {String} previous.REST
* @property {String} previous.GROUP * @property {String} previous.GROUP
@ -1120,6 +1130,7 @@ function OpenSeadragon( options ){
showHomeControl: true, //HOME showHomeControl: true, //HOME
showFullPageControl: true, //FULL showFullPageControl: true, //FULL
showRotationControl: false, //ROTATION showRotationControl: false, //ROTATION
showFlipControl: false, //FLIP
controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
@ -1141,6 +1152,9 @@ function OpenSeadragon( options ){
// INITIAL ROTATION // INITIAL ROTATION
degrees: 0, degrees: 0,
// INITIAL FLIP STATE
flipped: false,
// APPEARANCE // APPEARANCE
opacity: 1, opacity: 1,
preload: false, preload: false,
@ -1209,6 +1223,12 @@ function OpenSeadragon( options ){
HOVER: 'rotateright_hover.png', HOVER: 'rotateright_hover.png',
DOWN: 'rotateright_pressed.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: { previous: {
REST: 'previous_rest.png', REST: 'previous_rest.png',
GROUP: 'previous_grouphover.png', GROUP: 'previous_grouphover.png',

View File

@ -57,7 +57,8 @@ var I18N = {
NextPage: "Next page", NextPage: "Next page",
PreviousPage: "Previous page", PreviousPage: "Previous page",
RotateLeft: "Rotate left", 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, degrees: tiledImage.viewport.degrees,
useSketch: useSketch useSketch: useSketch
}); });
} else {
if(tiledImage._drawer.viewer.flipped) {
tiledImage._drawer._flip({});
}
} }
if (tiledImage.getRotation(true) % 360 !== 0) { if (tiledImage.getRotation(true) % 360 !== 0) {
tiledImage._drawer._offsetForRotation({ tiledImage._drawer._offsetForRotation({
@ -1969,6 +1973,10 @@ function drawTiles( tiledImage, lastDrawn ) {
} }
if (tiledImage.viewport.degrees !== 0) { if (tiledImage.viewport.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(useSketch); 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 {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] Initial flip/mirror state
* @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.
@ -1414,6 +1415,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,
@ -1674,6 +1676,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
onFullScreenHandler = $.delegate( this, onFullScreen ), onFullScreenHandler = $.delegate( this, onFullScreen ),
onRotateLeftHandler = $.delegate( this, onRotateLeft ), onRotateLeftHandler = $.delegate( this, onRotateLeft ),
onRotateRightHandler = $.delegate( this, onRotateRight ), onRotateRightHandler = $.delegate( this, onRotateRight ),
onFlipHandler = $.delegate( this, onFlip),
onFocusHandler = $.delegate( this, onFocus ), onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ), onBlurHandler = $.delegate( this, onBlur ),
navImages = this.navImages, navImages = this.navImages,
@ -1685,7 +1688,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if( this.zoomInButton || this.zoomOutButton || if( this.zoomInButton || this.zoomOutButton ||
this.homeButton || this.fullPageButton || this.homeButton || this.fullPageButton ||
this.rotateLeftButton || this.rotateRightButton ) { this.rotateLeftButton || this.rotateRightButton ||
this.flipButton ) {
//if we are binding to custom buttons then layout and //if we are binding to custom buttons then layout and
//grouping is the responsibility of the page author //grouping is the responsibility of the page author
useGroup = false; useGroup = false;
@ -1789,7 +1793,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
onFocus: onFocusHandler, onFocus: onFocusHandler,
onBlur: onBlurHandler 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 ) { if ( useGroup ) {
@ -2602,8 +2621,26 @@ function onCanvasKeyPress( event ) {
this.viewport.applyConstraints(); this.viewport.applyConstraints();
} }
return false; 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: default:
//console.log( 'navigator keycode %s', event.keyCode ); // console.log( 'navigator keycode %s', event.keyCode );
return true; return true;
} }
} else { } else {
@ -2620,6 +2657,9 @@ function onCanvasClick( event ) {
if ( !haveKeyboardFocus ) { if ( !haveKeyboardFocus ) {
this.canvas.focus(); this.canvas.focus();
} }
if(this.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
var canvasClickEventArgs = { var canvasClickEventArgs = {
tracker: event.eventSource, tracker: event.eventSource,
@ -2739,6 +2779,9 @@ function onCanvasDrag( event ) {
if( !this.panVertical ){ if( !this.panVertical ){
event.delta.y = 0; event.delta.y = 0;
} }
if(this.flipped){
event.delta.x = -event.delta.x;
}
if( this.constrainDuringPan ){ if( this.constrainDuringPan ){
var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() ); var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
@ -3064,6 +3107,10 @@ function onCanvasScroll( event ) {
if (deltaScrollTime > this.minScrollDeltaTime) { if (deltaScrollTime > this.minScrollDeltaTime) {
this._lastScrollTime = thisScrollTime; this._lastScrollTime = thisScrollTime;
if(this.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
if ( !event.preventDefaultAction && this.viewport ) { if ( !event.preventDefaultAction && this.viewport ) {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if ( gestureSettings.scrollToZoom ) { if ( gestureSettings.scrollToZoom ) {
@ -3429,12 +3476,21 @@ function onFullScreen() {
function onRotateLeft() { function onRotateLeft() {
if ( this.viewport ) { if ( this.viewport ) {
var currRotation = this.viewport.getRotation(); var currRotation = this.viewport.getRotation();
if ( this.flipped ){
if (currRotation === 270) {
currRotation = 0;
}
else {
currRotation += 90;
}
} else {
if (currRotation === 0) { if (currRotation === 0) {
currRotation = 270; currRotation = 270;
} }
else { else {
currRotation -= 90; currRotation -= 90;
} }
}
this.viewport.setRotation(currRotation); this.viewport.setRotation(currRotation);
} }
} }
@ -3445,16 +3501,38 @@ function onRotateLeft() {
function onRotateRight() { function onRotateRight() {
if ( this.viewport ) { if ( this.viewport ) {
var currRotation = this.viewport.getRotation(); var currRotation = this.viewport.getRotation();
if ( this.flipped ){
if (currRotation === 0) {
currRotation = 270;
}
else {
currRotation -= 90;
}
} else {
if (currRotation === 270) { if (currRotation === 270) {
currRotation = 0; currRotation = 0;
} }
else { else {
currRotation += 90; currRotation += 90;
} }
}
this.viewport.setRotation(currRotation); 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(){ function onPrevious(){
var previous = this._sequenceIndex - 1; var previous = this._sequenceIndex - 1;

View File

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