mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-17 16:47:19 +02:00
Merge pull request #1857 from alicevision/mug/exifOrientation
[ui] Viewer2D: support all Exif orientation tags
This commit is contained in:
commit
2c7547e493
3 changed files with 99 additions and 43 deletions
59
meshroom/ui/qml/Controls/ExifOrientedViewer.qml
Normal file
59
meshroom/ui/qml/Controls/ExifOrientedViewer.qml
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -9,3 +9,4 @@ Panel 1.0 Panel.qml
|
||||||
SearchBar 1.0 SearchBar.qml
|
SearchBar 1.0 SearchBar.qml
|
||||||
TabPanel 1.0 TabPanel.qml
|
TabPanel 1.0 TabPanel.qml
|
||||||
TextFileViewer 1.0 TextFileViewer.qml
|
TextFileViewer 1.0 TextFileViewer.qml
|
||||||
|
ExifOrientedViewer 1.0 ExifOrientedViewer.qml
|
||||||
|
|
|
@ -152,11 +152,27 @@ FocusScope {
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
function fit() {
|
function fit() {
|
||||||
|
// make sure the image is ready for use
|
||||||
|
if(!imgContainer.image)
|
||||||
|
return;
|
||||||
if(imgContainer.image.status != Image.Ready)
|
if(imgContainer.image.status != Image.Ready)
|
||||||
return;
|
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)
|
// for Exif orientation tags 5 to 8, a 90 degrees rotation is applied
|
||||||
imgContainer.y = Math.max((imgLayout.height - imgContainer.image.height * imgContainer.scale)*0.5, 0)
|
// 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) {
|
function tryLoadNode(node) {
|
||||||
|
@ -372,13 +388,17 @@ FocusScope {
|
||||||
Item {
|
Item {
|
||||||
id: imgContainer
|
id: imgContainer
|
||||||
transformOrigin: Item.TopLeft
|
transformOrigin: Item.TopLeft
|
||||||
|
property var orientationTag: m.imgMetadata ? m.imgMetadata["Orientation"] : 0
|
||||||
|
|
||||||
// qtAliceVision Image Viewer
|
// qtAliceVision Image Viewer
|
||||||
Loader {
|
ExifOrientedViewer {
|
||||||
id: floatImageViewerLoader
|
id: floatImageViewerLoader
|
||||||
active: root.aliceVisionPluginAvailable && (root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
|
active: root.aliceVisionPluginAvailable && (root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
|
||||||
visible: (floatImageViewerLoader.status === Loader.Ready) && active
|
visible: (floatImageViewerLoader.status === Loader.Ready) && active
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
orientationTag: imgContainer.orientationTag
|
||||||
|
xOrigin: imgContainer.width / 2
|
||||||
|
yOrigin: imgContainer.height / 2
|
||||||
property var fittedOnce: false
|
property var fittedOnce: false
|
||||||
property var previousWidth: 0
|
property var previousWidth: 0
|
||||||
property var previousHeight: 0
|
property var previousHeight: 0
|
||||||
|
@ -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: {
|
onActiveChanged: {
|
||||||
if (active) {
|
if (active) {
|
||||||
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
|
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
|
||||||
|
@ -442,7 +451,6 @@ FocusScope {
|
||||||
fittedOnce = false
|
fittedOnce = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// qtAliceVision Panorama Viewer
|
// qtAliceVision Panorama Viewer
|
||||||
|
@ -474,16 +482,18 @@ FocusScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple QML Image Viewer (using Qt or qtAliceVisionImageIO to load images)
|
// Simple QML Image Viewer (using Qt or qtAliceVisionImageIO to load images)
|
||||||
Loader {
|
ExifOrientedViewer {
|
||||||
id: qtImageViewerLoader
|
id: qtImageViewerLoader
|
||||||
active: !floatImageViewerLoader.active && !panoramaViewerLoader.active
|
active: !floatImageViewerLoader.active && !panoramaViewerLoader.active
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
orientationTag: imgContainer.orientationTag
|
||||||
|
xOrigin: imgContainer.width / 2
|
||||||
|
yOrigin: imgContainer.height / 2
|
||||||
sourceComponent: Image {
|
sourceComponent: Image {
|
||||||
id: qtImageViewer
|
id: qtImageViewer
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
smooth: false
|
smooth: false
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
autoTransform: true
|
|
||||||
onWidthChanged: if(status==Image.Ready) fit()
|
onWidthChanged: if(status==Image.Ready) fit()
|
||||||
source: getImageFile()
|
source: getImageFile()
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
|
@ -501,7 +511,6 @@ FocusScope {
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
smooth: parent.smooth
|
smooth: parent.smooth
|
||||||
fillMode: parent.fillMode
|
fillMode: parent.fillMode
|
||||||
autoTransform: parent.autoTransform
|
|
||||||
|
|
||||||
visible: qtImageViewer.status === Image.Loading
|
visible: qtImageViewer.status === Image.Loading
|
||||||
}
|
}
|
||||||
|
@ -522,22 +531,16 @@ FocusScope {
|
||||||
|
|
||||||
// FeatureViewer: display view extracted feature points
|
// FeatureViewer: display view extracted feature points
|
||||||
// note: requires QtAliceVision plugin - use a Loader to evaluate plugin availability at runtime
|
// note: requires QtAliceVision plugin - use a Loader to evaluate plugin availability at runtime
|
||||||
Loader {
|
ExifOrientedViewer {
|
||||||
id: featuresViewerLoader
|
id: featuresViewerLoader
|
||||||
active: displayFeatures.checked
|
active: displayFeatures.checked
|
||||||
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("FeatureExtraction").node : null
|
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("FeatureExtraction").node : null
|
||||||
|
width: imgContainer.width
|
||||||
// handle rotation/position based on available metadata
|
height: imgContainer.height
|
||||||
rotation: {
|
anchors.centerIn: parent
|
||||||
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0
|
orientationTag: imgContainer.orientationTag
|
||||||
switch(orientation) {
|
xOrigin: imgContainer.width / 2
|
||||||
case "6": return 90;
|
yOrigin: imgContainer.height / 2
|
||||||
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
|
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if(active) {
|
if(active) {
|
||||||
|
@ -556,21 +559,14 @@ FocusScope {
|
||||||
|
|
||||||
// FisheyeCircleViewer: display fisheye circle
|
// FisheyeCircleViewer: display fisheye circle
|
||||||
// note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime
|
// note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime
|
||||||
Loader {
|
ExifOrientedViewer {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
orientationTag: imgContainer.orientationTag
|
||||||
|
xOrigin: imgContainer.width / 2
|
||||||
|
yOrigin: imgContainer.height / 2
|
||||||
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("PanoramaInit").node : null
|
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("PanoramaInit").node : null
|
||||||
active: (displayFisheyeCircleLoader.checked && activeNode)
|
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 {
|
sourceComponent: CircleGizmo {
|
||||||
property bool useAuto: activeNode.attribute("estimateFisheyeCircle").value
|
property bool useAuto: activeNode.attribute("estimateFisheyeCircle").value
|
||||||
readOnly: useAuto
|
readOnly: useAuto
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue