Skip to content

Commit

Permalink
Merge pull request #1857 from alicevision/mug/exifOrientation
Browse files Browse the repository at this point in the history
[ui] Viewer2D: support all Exif orientation tags
  • Loading branch information
fabiencastan authored Jan 14, 2023
2 parents 8ab563a + 1709327 commit 2c7547e
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 43 deletions.
59 changes: 59 additions & 0 deletions meshroom/ui/qml/Controls/ExifOrientedViewer.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import QtQuick 2.11

/**
* Loader with a predefined transform to orient its content according to the provided Exif tag.
* Useful when displaying images and overlayed information in the Viewer2D.
*
* Usage:
* - set the orientationTag property to specify Exif orientation tag.
* - set the xOrigin/yOrigin properties to specify the transform origin.
*/
Loader {
property var orientationTag: undefined

property real xOrigin: 0
property real yOrigin: 0

transform: [
Rotation {
angle: {
switch(orientationTag) {
case "3":
return 180;
case "4":
return 180;
case "5":
return 90;
case "6":
return 90;
case "7":
return -90;
case "8":
return -90;
default:
return 0;
}
}
origin.x: xOrigin
origin.y: yOrigin
},
Scale {
xScale : {
switch(orientationTag) {
case "2":
return -1;
case "4":
return -1;
case "5":
return -1;
case "7":
return -1;
default:
return 1;
}
}
origin.x: xOrigin
origin.y: yOrigin
}
]
}
1 change: 1 addition & 0 deletions meshroom/ui/qml/Controls/qmldir
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Panel 1.0 Panel.qml
SearchBar 1.0 SearchBar.qml
TabPanel 1.0 TabPanel.qml
TextFileViewer 1.0 TextFileViewer.qml
ExifOrientedViewer 1.0 ExifOrientedViewer.qml
82 changes: 39 additions & 43 deletions meshroom/ui/qml/Viewer/Viewer2D.qml
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,27 @@ FocusScope {

// functions
function fit() {
// make sure the image is ready for use
if(!imgContainer.image)
return;
if(imgContainer.image.status != Image.Ready)
return;
imgContainer.scale = Math.min(imgLayout.width / imgContainer.image.width, root.height / imgContainer.image.height)
imgContainer.x = Math.max((imgLayout.width - imgContainer.image.width * imgContainer.scale)*0.5, 0)
imgContainer.y = Math.max((imgLayout.height - imgContainer.image.height * imgContainer.scale)*0.5, 0)

// for Exif orientation tags 5 to 8, a 90 degrees rotation is applied
// therefore image dimensions must be inverted
let dimensionsInverted = ["5", "6", "7", "8"].includes(imgContainer.orientationTag);
let orientedWidth = dimensionsInverted ? imgContainer.image.height : imgContainer.image.width;
let orientedHeight = dimensionsInverted ? imgContainer.image.width : imgContainer.image.height;

// fit oriented image
imgContainer.scale = Math.min(imgLayout.width / orientedWidth, root.height / orientedHeight);
imgContainer.x = Math.max((imgLayout.width - orientedWidth * imgContainer.scale)*0.5, 0);
imgContainer.y = Math.max((imgLayout.height - orientedHeight * imgContainer.scale)*0.5, 0);

// correct position when image dimensions are inverted
// so that container center corresponds to image center
imgContainer.x += (orientedWidth - imgContainer.image.width) * 0.5 * imgContainer.scale;
imgContainer.y += (orientedHeight - imgContainer.image.height) * 0.5 * imgContainer.scale;
}

function tryLoadNode(node) {
Expand Down Expand Up @@ -372,13 +388,17 @@ FocusScope {
Item {
id: imgContainer
transformOrigin: Item.TopLeft
property var orientationTag: m.imgMetadata ? m.imgMetadata["Orientation"] : 0

// qtAliceVision Image Viewer
Loader {
ExifOrientedViewer {
id: floatImageViewerLoader
active: root.aliceVisionPluginAvailable && (root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
visible: (floatImageViewerLoader.status === Loader.Ready) && active
anchors.centerIn: parent
orientationTag: imgContainer.orientationTag
xOrigin: imgContainer.width / 2
yOrigin: imgContainer.height / 2
property var fittedOnce: false
property var previousWidth: 0
property var previousHeight: 0
Expand All @@ -403,17 +423,6 @@ FocusScope {
}
}

// handle rotation/position based on available metadata
rotation: {
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0

switch (orientation) {
case "6": return 90;
case "8": return -90;
default: return 0;
}
}

onActiveChanged: {
if (active) {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
Expand Down Expand Up @@ -442,7 +451,6 @@ FocusScope {
fittedOnce = false
}
}

}

// qtAliceVision Panorama Viewer
Expand Down Expand Up @@ -474,16 +482,18 @@ FocusScope {
}

// Simple QML Image Viewer (using Qt or qtAliceVisionImageIO to load images)
Loader {
ExifOrientedViewer {
id: qtImageViewerLoader
active: !floatImageViewerLoader.active && !panoramaViewerLoader.active
anchors.centerIn: parent
orientationTag: imgContainer.orientationTag
xOrigin: imgContainer.width / 2
yOrigin: imgContainer.height / 2
sourceComponent: Image {
id: qtImageViewer
asynchronous: true
smooth: false
fillMode: Image.PreserveAspectFit
autoTransform: true
onWidthChanged: if(status==Image.Ready) fit()
source: getImageFile()
onStatusChanged: {
Expand All @@ -501,7 +511,6 @@ FocusScope {
asynchronous: true
smooth: parent.smooth
fillMode: parent.fillMode
autoTransform: parent.autoTransform

visible: qtImageViewer.status === Image.Loading
}
Expand All @@ -522,22 +531,16 @@ FocusScope {

// FeatureViewer: display view extracted feature points
// note: requires QtAliceVision plugin - use a Loader to evaluate plugin availability at runtime
Loader {
ExifOrientedViewer {
id: featuresViewerLoader
active: displayFeatures.checked
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("FeatureExtraction").node : null

// handle rotation/position based on available metadata
rotation: {
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0
switch(orientation) {
case "6": return 90;
case "8": return -90;
default: return 0;
}
}
x: (imgContainer.image && rotation === 90) ? imgContainer.image.paintedWidth : 0
y: (imgContainer.image && rotation === -90) ? imgContainer.image.paintedHeight : 0
width: imgContainer.width
height: imgContainer.height
anchors.centerIn: parent
orientationTag: imgContainer.orientationTag
xOrigin: imgContainer.width / 2
yOrigin: imgContainer.height / 2

onActiveChanged: {
if(active) {
Expand All @@ -556,21 +559,14 @@ FocusScope {

// FisheyeCircleViewer: display fisheye circle
// note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime
Loader {
ExifOrientedViewer {
anchors.centerIn: parent
orientationTag: imgContainer.orientationTag
xOrigin: imgContainer.width / 2
yOrigin: imgContainer.height / 2
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("PanoramaInit").node : null
active: (displayFisheyeCircleLoader.checked && activeNode)

// handle rotation/position based on available metadata
rotation: {
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0
switch(orientation) {
case "6": return 90;
case "8": return -90;
default: return 0;
}
}

sourceComponent: CircleGizmo {
property bool useAuto: activeNode.attribute("estimateFisheyeCircle").value
readOnly: useAuto
Expand Down

0 comments on commit 2c7547e

Please sign in to comment.