[ui] Viewer2D: refactoring to simplify layouts

* fix mousearea order
* add alpha backgroud
* move HDR as a tool button

Conflicts:
	meshroom/ui/qml/Viewer/CircleGizmo.qml
	meshroom/ui/qml/Viewer/Viewer2D.qml
This commit is contained in:
Fabien Castan 2020-03-04 17:24:51 +01:00
parent c9c24b7ff0
commit d70c625fac
4 changed files with 323 additions and 290 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

View file

@ -46,8 +46,8 @@ FloatingPane {
id: displayModeCB
flat: true
Layout.fillWidth: true
model: featuresViewer.displayModes
onActivated: featuresViewer.displayMode = currentIndex
model: root.featuresViewer.displayModes
onActivated: root.featuresViewer.displayMode = currentIndex
}
}
}
@ -67,12 +67,12 @@ FloatingPane {
implicitHeight: contentHeight
implicitWidth: contentItem.childrenRect.width
model: featuresViewer !== null ? featuresViewer.model : 0
model: root.featuresViewer !== null ? root.featuresViewer.model : 0
delegate: RowLayout {
id: featureType
property var viewer: featuresViewer.itemAt(index)
property var viewer: root.featuresViewer.itemAt(index)
spacing: 4
// Visibility toogle
@ -86,10 +86,10 @@ FloatingPane {
ColorChart {
implicitWidth: 12
implicitHeight: implicitWidth
colors: featuresViewer.colors
colors: root.featuresViewer.colors
currentIndex: featureType.viewer.colorIndex
// offset FeaturesViewer color set when changing the color of one feature type
onColorPicked: featuresViewer.colorOffset = colorIndex - index
// offset featuresViewer color set when changing the color of one feature type
onColorPicked: root.featuresViewer.colorOffset = colorIndex - index
}
// Feature type name
Label {

View file

@ -13,9 +13,9 @@ FocusScope {
property var metadata
property var viewIn3D
property Component floatViewerComp: Qt.createComponent("FloatImage.qml", imgContainer)
property Component floatViewerComp: Qt.createComponent("FloatImage.qml")
readonly property bool floatViewerAvailable: floatViewerComp.status === Component.Ready
property bool useFloatImageViewer: false
property alias useFloatImageViewer: displayHDR.checked
function clear()
{
@ -31,148 +31,6 @@ FocusScope {
}
}
// functions
function fit() {
if(imgContainer.image.status != Image.Ready)
return;
imgContainer.scale = Math.min(imgContainer.width/imgContainer.image.width, imgContainer.height/imgContainer.image.height)
imgContainer.x = Math.max((imgContainer.width-imgContainer.width*imgContainer.scale)*0.5, 0)
imgContainer.y = Math.max((imgContainer.height-imgContainer.height*imgContainer.scale)*0.5, 0)
}
function getImageFile(type) {
if (type == "image") {
return root.source;
} else if (_reconstruction.depthMap != undefined && _reconstruction.selectedViewId >= 0) {
return Filepath.stringToUrl(_reconstruction.depthMap.internalFolder+_reconstruction.selectedViewId+"_"+type+"Map.exr");
}
return "";
}
// context menu
property Component contextMenu: Menu {
MenuItem {
text: "Fit"
onTriggered: fit()
}
MenuItem {
text: "Zoom 100%"
onTriggered: {
imgContainer.scale = 1
imgContainer.x = Math.max((imgContainer.width-imgContainer.width*imgContainer.scale)*0.5, 0)
imgContainer.y = Math.max((imgContainer.height-imgContainer.height*imgContainer.scale)*0.5, 0)
}
}
}
// Image
Item {
id: imgContainer
transformOrigin: Item.TopLeft
width: parent.width
height: parent.height
property var image: qtImageViewerLoader.active ? qtImageViewerLoader.item : floatImageViewerLoader.item
// qtAliceVision Image Viewer
Loader {
id: floatImageViewerLoader
active: root.useFloatImageViewer
visible: (floatImageViewerLoader.status === Loader.Ready)
anchors.centerIn: parent
Component.onCompleted: {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
// Note: It does not work to use previously created component,
// so we re-create it with setSource.
// floatViewerComp.createObject(floatImageViewerLoader, {
setSource("FloatImage.qml", {
'source': Qt.binding(function() { return getImageFile(imageType.type); }),
'gamma': Qt.binding(function() { return imageToolbar.gammaValue; }),
'offset': Qt.binding(function() { return imageToolbar.offsetValue; }),
'channelModeString': Qt.binding(function() { return imageToolbar.channelModeValue; }),
})
}
}
// Simple QML Image Viewer (using Qt or qtOIIO to load images)
Loader {
id: qtImageViewerLoader
active: (!root.useFloatImageViewer) || (floatImageViewerLoader.status === Loader.Error)
anchors.centerIn: parent
sourceComponent: Image {
id: qtImageViewer
asynchronous: true
smooth: false
fillMode: Image.PreserveAspectFit
autoTransform: true
onWidthChanged: if(status==Image.Ready) fit()
source: getImageFile(imageType.type)
onStatusChanged: {
// update cache source when image is loaded
if(status === Image.Ready)
qtImageViewerCache.source = source
}
// Image cache of the last loaded image
// Only visible when the main one is loading, to keep an image
// displayed at all time and smoothen transitions
Image {
id: qtImageViewerCache
anchors.fill: parent
asynchronous: true
smooth: parent.smooth
fillMode: parent.fillMode
autoTransform: parent.autoTransform
visible: qtImageViewer.status === Image.Loading
}
}
}
// FeatureViewer: display view extracted feature points
// note: requires QtAliceVision plugin - use a Loader to evaluate plugin avaibility at runtime
Loader {
id: featuresViewerLoader
active: displayFeatures.checked
// handle rotation/position based on available metadata
rotation: {
var orientation = metadata ? metadata["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
Component.onCompleted: {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
setSource("FeaturesViewer.qml", {
'active': Qt.binding(function() { return displayFeatures.checked; }),
'viewId': Qt.binding(function() { return _reconstruction.selectedViewId; }),
'model': Qt.binding(function() { return _reconstruction.featureExtraction.attribute("describerTypes").value; }),
'folder': Qt.binding(function() { return Filepath.stringToUrl(_reconstruction.featureExtraction.attribute("output").value); }),
})
}
}
}
// Busy indicator
BusyIndicator {
anchors.centerIn: parent
// running property binding seems broken, only dynamic binding assignment works
Component.onCompleted: {
running = Qt.binding(function() { return imgContainer.image && imgContainer.image.status === Image.Loading })
}
// disable the visibility when unused to avoid stealing the mouseEvent to the image color picker
visible: running
}
// mouse area
MouseArea {
anchors.fill: parent
@ -203,12 +61,44 @@ FocusScope {
}
}
// functions
function fit() {
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)
// console.warn("fit: imgLayout.width: " + imgContainer.scale + ", imgContainer.image.width: " + imgContainer.image.width)
// console.warn("fit: imgContainer.scale: " + imgContainer.scale + ", x: " + imgContainer.x + ", y: " + imgContainer.y)
}
function getImageFile(type) {
if (type == "image") {
return root.source;
} else if (_reconstruction.depthMap != undefined && _reconstruction.selectedViewId >= 0) {
return Filepath.stringToUrl(_reconstruction.depthMap.internalFolder+_reconstruction.selectedViewId+"_"+type+"Map.exr");
}
return "";
}
// context menu
property Component contextMenu: Menu {
MenuItem {
text: "Fit"
onTriggered: fit()
}
MenuItem {
text: "Zoom 100%"
onTriggered: {
imgContainer.scale = 1
imgContainer.x = Math.max((imgLayout.width-imgContainer.width*imgContainer.scale)*0.5, 0)
imgContainer.y = Math.max((imgLayout.height-imgContainer.height*imgContainer.scale)*0.5, 0)
}
}
}
ColumnLayout {
id: topToolbar
anchors.top: parent.top
anchors.margins: 0
width: parent.width
spacing: 0
anchors.fill: parent
ImageToolbar {
id: imageToolbar
@ -232,147 +122,300 @@ FocusScope {
}
}
FloatingPane {
id: imagePathToolbar
anchors.margins: 0
radius: 0
padding: 4
visible: displayImagePathAction.checked
// Image
Item {
id: imgLayout
Layout.fillWidth: true
RowLayout {
Layout.fillHeight: true
clip: true
Image {
id: alphaBackground
anchors.fill: parent
// selectable filepath to source image
TextField {
padding: 0
background: Item {}
horizontalAlignment: TextInput.AlignLeft
Layout.fillWidth: true
font.pointSize: 8
readOnly: true
selectByMouse: true
text: Filepath.urlToString(getImageFile(imageType.type))
}
// show which depthmap node is active
Label {
id: depthMapNodeName
visible: (_reconstruction.depthMap != undefined) && (imageType.type != "image")
text: (_reconstruction.depthMap != undefined ? _reconstruction.depthMap.label : "")
font.pointSize: 8
horizontalAlignment: TextInput.AlignLeft
Layout.fillWidth: false
Layout.preferredWidth: contentWidth
}
}
}
}
// Image Metadata overlay Pane
ImageMetadataView {
width: 350
anchors {
top: topToolbar.bottom
right: parent.right
bottom: bottomToolbar.top
}
visible: metadataCB.checked
// only load metadata model if visible
metadata: visible ? root.metadata : {}
}
Loader {
id: featuresOverlay
anchors.bottom: bottomToolbar.top
anchors.left: parent.left
anchors.margins: 2
active: displayFeatures.checked
sourceComponent: FeaturesInfoOverlay {
featureExtractionNode: _reconstruction.featureExtraction
pluginStatus: featuresViewerLoader.status
featuresViewer: featuresViewerLoader.item
}
}
FloatingPane {
id: bottomToolbar
anchors.bottom: parent.bottom
anchors.margins: 0
width: parent.width
topPadding: 2
bottomPadding: topPadding
RowLayout {
anchors.fill: parent
// zoom label
Label {
text: ((imgContainer.image && (imgContainer.image.status == Image.Ready)) ? imgContainer.scale.toFixed(2) : "1.00") + "x"
state: "xsmall"
}
MaterialToolButton {
id: displayFeatures
font.pointSize: 11
ToolTip.text: "Display Features"
checkable: true
text: MaterialIcons.scatter_plot
visible: displayAlphaBackground.checked
fillMode: Image.Tile
horizontalAlignment: Image.AlignLeft
verticalAlignment: Image.AlignTop
source: "../../img/checkerboard_light.png"
scale: 4
smooth: false
}
Item {
Layout.fillWidth: true
Label {
id: resolutionLabel
text: imgContainer.image ? (imgContainer.image.sourceSize.width + "x" + imgContainer.image.sourceSize.height) : ""
id: imgContainer
transformOrigin: Item.TopLeft
// qtAliceVision Image Viewer
Loader {
id: floatImageViewerLoader
active: root.useFloatImageViewer
visible: (floatImageViewerLoader.status === Loader.Ready)
anchors.centerIn: parent
elide: Text.ElideMiddle
Component.onCompleted: {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
// Note: It does not work to use previously created component,
// so we re-create it with setSource.
// floatViewerComp.createObject(floatImageViewerLoader, {
setSource("FloatImage.qml", {
'source': Qt.binding(function() { return getImageFile(imageType.type); }),
'gamma': Qt.binding(function() { return imageToolbar.gammaValue; }),
'offset': Qt.binding(function() { return imageToolbar.offsetValue; }),
'channelModeString': Qt.binding(function() { return imageToolbar.channelModeValue; }),
})
}
}
// Simple QML Image Viewer (using Qt or qtOIIO to load images)
Loader {
id: qtImageViewerLoader
active: (!root.useFloatImageViewer) || (floatImageViewerLoader.status === Loader.Error)
anchors.centerIn: parent
sourceComponent: Image {
id: qtImageViewer
asynchronous: true
smooth: false
fillMode: Image.PreserveAspectFit
autoTransform: true
onWidthChanged: if(status==Image.Ready) fit()
source: getImageFile(imageType.type)
onStatusChanged: {
// update cache source when image is loaded
if(status === Image.Ready)
qtImageViewerCache.source = source
}
// Image cache of the last loaded image
// Only visible when the main one is loading, to keep an image
// displayed at all time and smoothen transitions
Image {
id: qtImageViewerCache
anchors.fill: parent
asynchronous: true
smooth: parent.smooth
fillMode: parent.fillMode
autoTransform: parent.autoTransform
visible: qtImageViewer.status === Image.Loading
}
}
}
property var image: qtImageViewerLoader.active ? qtImageViewerLoader.item : floatImageViewerLoader.item
width: image ? image.width : 1
height: image ? image.height : 1
scale: 1.0
// FeatureViewer: display view extracted feature points
// note: requires QtAliceVision plugin - use a Loader to evaluate plugin avaibility at runtime
Loader {
id: featuresViewerLoader
active: displayFeatures.checked
// handle rotation/position based on available metadata
rotation: {
var orientation = metadata ? metadata["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
Component.onCompleted: {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
setSource("FeaturesViewer.qml", {
'active': Qt.binding(function() { return displayFeatures.checked; }),
'viewId': Qt.binding(function() { return _reconstruction.selectedViewId; }),
'model': Qt.binding(function() { return _reconstruction.featureExtraction.attribute("describerTypes").value; }),
'folder': Qt.binding(function() { return Filepath.stringToUrl(_reconstruction.featureExtraction.attribute("output").value); }),
})
}
}
}
ComboBox {
id: imageType
// set min size to 5 characters + one margin for the combobox
Layout.minimumWidth: 6.0 * Qt.application.font.pixelSize
Layout.preferredWidth: Layout.minimumWidth
flat: true
property var types: ["image", "depth", "sim"]
property string type: types[currentIndex]
ColumnLayout {
anchors.fill: parent
spacing: 0
FloatingPane {
id: imagePathToolbar
Layout.fillWidth: true
// Layout.fillHeight: true
Layout.preferredHeight: childrenRect.height
visible: displayImagePathAction.checked
model: types
enabled: _reconstruction.depthMap != undefined
}
RowLayout {
width: parent.width
height: childrenRect.height
MaterialToolButton {
font.pointSize: 11
enabled: _reconstruction.depthMap != undefined
ToolTip.text: "View Depth Map in 3D (" + (_reconstruction.depthMap != undefined ? _reconstruction.depthMap.label : "No DepthMap Node Selected") + ")"
text: MaterialIcons.input
// selectable filepath to source image
TextField {
padding: 0
background: Item {}
horizontalAlignment: TextInput.AlignLeft
Layout.fillWidth: true
height: contentHeight
font.pointSize: 8
readOnly: true
selectByMouse: true
text: Filepath.urlToString(getImageFile(imageType.type))
}
onClicked: {
root.viewIn3D(root.getImageFile("depth"))
// show which depthmap node is active
Label {
id: depthMapNodeName
visible: (_reconstruction.depthMap != undefined) && (imageType.type != "image")
text: (_reconstruction.depthMap != undefined ? _reconstruction.depthMap.label : "")
font.pointSize: 8
horizontalAlignment: TextInput.AlignLeft
Layout.fillWidth: false
Layout.preferredWidth: contentWidth
height: contentHeight
}
}
}
}
Item {
id: imgPlaceholder
Layout.fillWidth: true
Layout.fillHeight: true
ToolButton {
id: metadataCB
padding: 3
// Image Metadata overlay Pane
ImageMetadataView {
width: 350
anchors {
top: parent.top
right: parent.right
bottom: parent.bottom
}
font.family: MaterialIcons.fontFamily
text: MaterialIcons.info_outline
visible: metadataCB.checked
// only load metadata model if visible
metadata: visible ? root.metadata : {}
}
ToolTip.text: "Image Metadata"
ToolTip.visible: hovered
Loader {
id: featuresOverlay
anchors {
bottom: parent.bottom
left: parent.left
margins: 2
}
active: displayFeatures.checked
font.pointSize: 12
smooth: false
flat: true
checkable: enabled
sourceComponent: FeaturesInfoOverlay {
featureExtractionNode: _reconstruction.featureExtraction
pluginStatus: featuresViewerLoader.status
featuresViewer: featuresViewerLoader.item
}
}
}
FloatingPane {
id: bottomToolbar
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
RowLayout {
anchors.fill: parent
// zoom label
Label {
text: ((imgContainer.image && (imgContainer.image.status == Image.Ready)) ? imgContainer.scale.toFixed(2) : "1.00") + "x"
state: "xsmall"
}
MaterialToolButton {
id: displayAlphaBackground
font.pointSize: 11
ToolTip.text: "Alpha Background"
checkable: true
text: MaterialIcons.texture
}
MaterialToolButton {
id: displayHDR
font.pointSize: 20
padding: 0
ToolTip.text: "HDR Floating Point Viewer"
text: MaterialIcons.hdr_on
checkable: true
checked: false
enabled: root.floatViewerAvailable
}
MaterialToolButton {
id: displayFeatures
font.pointSize: 11
ToolTip.text: "Display Features"
checkable: true
text: MaterialIcons.scatter_plot
}
Item {
Layout.fillWidth: true
Label {
id: resolutionLabel
text: imgContainer.image ? (imgContainer.image.sourceSize.width + "x" + imgContainer.image.sourceSize.height) : ""
anchors.centerIn: parent
elide: Text.ElideMiddle
}
}
ComboBox {
id: imageType
// set min size to 5 characters + one margin for the combobox
Layout.minimumWidth: 6.0 * Qt.application.font.pixelSize
Layout.preferredWidth: Layout.minimumWidth
flat: true
property var types: ["image", "depth", "sim"]
property string type: types[currentIndex]
model: types
enabled: _reconstruction.depthMap != undefined
}
MaterialToolButton {
font.pointSize: 11
enabled: _reconstruction.depthMap != undefined
ToolTip.text: "View Depth Map in 3D (" + (_reconstruction.depthMap != undefined ? _reconstruction.depthMap.label : "No DepthMap Node Selected") + ")"
text: MaterialIcons.input
onClicked: {
root.viewIn3D(root.getImageFile("depth"))
}
}
ToolButton {
id: metadataCB
padding: 3
font.family: MaterialIcons.fontFamily
text: MaterialIcons.info_outline
ToolTip.text: "Image Metadata"
ToolTip.visible: hovered
font.pointSize: 12
smooth: false
flat: true
checkable: enabled
enabled: _reconstruction.selectedViewId >= 0
}
}
}
}
}
}
// Busy indicator
BusyIndicator {
anchors.centerIn: parent
// running property binding seems broken, only dynamic binding assignment works
Component.onCompleted: {
running = Qt.binding(function() { return imgContainer.image && imgContainer.image.status === Image.Loading })
}
// disable the visibility when unused to avoid stealing the mouseEvent to the image color picker
visible: running
}
}

View file

@ -95,21 +95,12 @@ Item {
id: imageViewerMenu
y: parent.height
x: -width + parent.width
Action {
id: useFloatImageViewerAction
text: "Use HDR Image Viewer"
checkable: true
checked: false
enabled: viewer2D.floatViewerAvailable
// tooltip: "Floating Point Image Viewer allows to visualize image with the full color dynamic. It is useful for HDR or RAW visualization. (Requires a plugin)"
}
Action {
id: displayImageToolBarAction
text: "Display HDR Toolbar"
checkable: true
checked: true
enabled: useFloatImageViewerAction.checked
enabled: viewer2D.useFloatImageViewer
}
Action {
id: displayImagePathAction
@ -124,7 +115,6 @@ Item {
Viewer2D {
id: viewer2D
anchors.fill: parent
useFloatImageViewer: useFloatImageViewerAction.checked
viewIn3D: root.load3DMedia