Merge pull request #1857 from alicevision/mug/exifOrientation

[ui] Viewer2D: support all Exif orientation tags
This commit is contained in:
Fabien Castan 2023-01-14 21:25:34 +00:00 committed by GitHub
commit 2c7547e493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 43 deletions

View 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
}
]
}

View file

@ -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

View file

@ -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