diff --git a/meshroom/ui/qml/Controls/ExifOrientedViewer.qml b/meshroom/ui/qml/Controls/ExifOrientedViewer.qml new file mode 100644 index 00000000..9b588b48 --- /dev/null +++ b/meshroom/ui/qml/Controls/ExifOrientedViewer.qml @@ -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 + } + ] +} diff --git a/meshroom/ui/qml/Controls/qmldir b/meshroom/ui/qml/Controls/qmldir index c1e59083..f4c247b1 100644 --- a/meshroom/ui/qml/Controls/qmldir +++ b/meshroom/ui/qml/Controls/qmldir @@ -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 diff --git a/meshroom/ui/qml/Viewer/Viewer2D.qml b/meshroom/ui/qml/Viewer/Viewer2D.qml index fe39a374..3829648d 100644 --- a/meshroom/ui/qml/Viewer/Viewer2D.qml +++ b/meshroom/ui/qml/Viewer/Viewer2D.qml @@ -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) { @@ -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 @@ -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 @@ -442,7 +451,6 @@ FocusScope { fittedOnce = false } } - } // qtAliceVision Panorama Viewer @@ -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: { @@ -501,7 +511,6 @@ FocusScope { asynchronous: true smooth: parent.smooth fillMode: parent.fillMode - autoTransform: parent.autoTransform visible: qtImageViewer.status === Image.Loading } @@ -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) { @@ -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