mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-03 20:26:49 +02:00
[ui] add ImageMetadataView + integration in Viewer2D
* display image metadata as a sorted table view with filtering * 2DViewer: new bottom toolbar with metadata toggle + image resolution
This commit is contained in:
parent
d91601ca8e
commit
53764812bd
5 changed files with 287 additions and 10 deletions
|
@ -14,6 +14,7 @@ Panel {
|
||||||
|
|
||||||
property variant cameraInits
|
property variant cameraInits
|
||||||
property variant cameraInit
|
property variant cameraInit
|
||||||
|
readonly property alias currentItem: grid.currentItem
|
||||||
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
|
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
|
||||||
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
|
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
|
||||||
signal removeImageRequest(var attribute)
|
signal removeImageRequest(var attribute)
|
||||||
|
|
210
meshroom/ui/qml/Viewer/ImageMetadataView.qml
Normal file
210
meshroom/ui/qml/Viewer/ImageMetadataView.qml
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
import MaterialIcons 2.2
|
||||||
|
import QtPositioning 5.8
|
||||||
|
import QtLocation 5.9
|
||||||
|
|
||||||
|
import Utils 1.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageMetadataView displays a JSON model representing an image"s metadata as a ListView.
|
||||||
|
*/
|
||||||
|
Pane {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias metadata: metadataModel.metadata
|
||||||
|
property var coordinates: QtPositioning.coordinate()
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
padding: 4
|
||||||
|
|
||||||
|
SystemPalette { id: palette }
|
||||||
|
|
||||||
|
background: Rectangle { color: palette.window; opacity: 0.9 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert GPS metadata to degree coordinates.
|
||||||
|
*
|
||||||
|
* GPS coordinates in metadata can be store in 3 forms:
|
||||||
|
* (degrees), (degrees, minutes), (degrees, minutes, seconds)
|
||||||
|
*/
|
||||||
|
function gpsMetadataToCoordinates(value, ref)
|
||||||
|
{
|
||||||
|
var values = value.split(",")
|
||||||
|
var result = 0
|
||||||
|
for(var i=0; i < values.length; ++i)
|
||||||
|
{
|
||||||
|
// divide each component by the corresponding power of 60
|
||||||
|
// 1 for degree, 60 for minutes, 3600 for seconds
|
||||||
|
result += Number(values[i]) / Math.pow(60, i)
|
||||||
|
}
|
||||||
|
// handle opposite reference: South (latitude) or West (longitude)
|
||||||
|
return (ref === "S" || ref === "W") ? -result : result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get GPS coordinates from metadata
|
||||||
|
function getGPSCoordinates(metadata)
|
||||||
|
{
|
||||||
|
// GPS data available
|
||||||
|
if(metadata["GPS:Longitude"] != undefined && metadata["GPS:Latitude"] != undefined)
|
||||||
|
{
|
||||||
|
var latitude = gpsMetadataToCoordinates(metadata["GPS:Latitude"], metadata["GPS:LatitudeRef"])
|
||||||
|
var longitude = gpsMetadataToCoordinates(metadata["GPS:Longitude"], metadata["GPS:LongitudeRef"])
|
||||||
|
var altitude = metadata["GPS:Altitude"] || 0
|
||||||
|
return QtPositioning.coordinate(latitude, longitude, altitude)
|
||||||
|
}
|
||||||
|
// GPS data unavailable: reset coordinates to default value
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return QtPositioning.coordinate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata model
|
||||||
|
// Available roles for child items:
|
||||||
|
// - group: metadata group if any, "-" otherwise
|
||||||
|
// - key: metadata key
|
||||||
|
// - value: metadata value
|
||||||
|
// - raw: a sortable/filterable representation of the metadata as "group:key=value"
|
||||||
|
ListModel {
|
||||||
|
id: metadataModel
|
||||||
|
property var metadata: ({})
|
||||||
|
|
||||||
|
// reset model when metadata changes
|
||||||
|
onMetadataChanged: {
|
||||||
|
metadataModel.clear()
|
||||||
|
var entries = []
|
||||||
|
// prepare data to populate the model from the input metadata object
|
||||||
|
for(var key in metadata)
|
||||||
|
{
|
||||||
|
var entry = {}
|
||||||
|
entry["raw"] = key
|
||||||
|
// split on ":" to get group and key
|
||||||
|
var sKey = key.split(":", 2)
|
||||||
|
if(sKey.length === 2)
|
||||||
|
{
|
||||||
|
entry["group"] = sKey[0]
|
||||||
|
entry["key"] = sKey[1]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// set default group to something convenient for sorting
|
||||||
|
entry["group"] = "-"
|
||||||
|
entry["key"] = key
|
||||||
|
}
|
||||||
|
entry["value"] = metadata[key]
|
||||||
|
entry["raw"] = entry["group"] + ":" + entry["key"] + "=" + entry["value"]
|
||||||
|
entries.push(entry)
|
||||||
|
}
|
||||||
|
// reset the model with prepared data (limit to one update event)
|
||||||
|
metadataModel.append(entries)
|
||||||
|
coordinates = getGPSCoordinates(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button {
|
||||||
|
// onClicked: {
|
||||||
|
// if(sortedMetadataModel.sortOrder == Qt.DescendingOrder)
|
||||||
|
// sortedMetadataModel.sortOrder = Qt.AscendingOrder
|
||||||
|
// else
|
||||||
|
// sortedMetadataModel.sortOrder = Qt.DescendingOrder
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Background WheelEvent grabber
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.MiddleButton
|
||||||
|
onWheel: wheel.accepted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortFilter delegate over the metadataModel
|
||||||
|
SortFilterDelegateModel {
|
||||||
|
id: sortedMetadataModel
|
||||||
|
model: metadataModel
|
||||||
|
sortRole: "raw"
|
||||||
|
filterRole: "raw"
|
||||||
|
textFilter: filter.text
|
||||||
|
delegate: RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
Label {
|
||||||
|
text: key
|
||||||
|
leftPadding: 6
|
||||||
|
rightPadding: 4
|
||||||
|
Layout.preferredWidth: sizeHandle.x
|
||||||
|
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: value
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Layout
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// Search toolbar
|
||||||
|
RowLayout {
|
||||||
|
Label {
|
||||||
|
text: MaterialIcons.search
|
||||||
|
font.family: MaterialIcons.fontFamily
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: filter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
z: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata ListView
|
||||||
|
ListView {
|
||||||
|
id: metadataView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
model: sortedMetadataModel
|
||||||
|
spacing: 3
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
// Categories resize handle
|
||||||
|
Rectangle {
|
||||||
|
id: sizeHandle
|
||||||
|
height: parent.contentHeight
|
||||||
|
width: 1
|
||||||
|
color: palette.mid
|
||||||
|
x: parent.width * 0.4
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -4
|
||||||
|
cursorShape: Qt.SizeHorCursor
|
||||||
|
drag {
|
||||||
|
target: parent
|
||||||
|
axis: Drag.XAxis
|
||||||
|
threshold: 0
|
||||||
|
minimumX: metadataView.width * 0.2
|
||||||
|
maximumX: metadataView.width * 0.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Display section based on metadata group
|
||||||
|
section.property: "group"
|
||||||
|
section.delegate: Pane {
|
||||||
|
width: parent.width
|
||||||
|
padding: 3
|
||||||
|
background: null
|
||||||
|
|
||||||
|
Label {
|
||||||
|
width: parent.width
|
||||||
|
padding: 2
|
||||||
|
background: Rectangle { color: palette.mid }
|
||||||
|
text: section
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollBar.vertical: ScrollBar{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
import QtQuick.Controls 2.0
|
import QtQuick.Controls 2.0
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
import MaterialIcons 2.2
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
property alias source: image.source
|
property alias source: image.source
|
||||||
|
property var metadata
|
||||||
|
|
||||||
// slots
|
// slots
|
||||||
Keys.onPressed: {
|
Keys.onPressed: {
|
||||||
|
@ -81,7 +83,7 @@ FocusScope {
|
||||||
property double factor: 1.2
|
property double factor: 1.2
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
onPressed: {
|
onPressed: {
|
||||||
root.forceActiveFocus();
|
image.forceActiveFocus()
|
||||||
if(mouse.button & Qt.MiddleButton)
|
if(mouse.button & Qt.MiddleButton)
|
||||||
drag.target = image // start drag
|
drag.target = image // start drag
|
||||||
}
|
}
|
||||||
|
@ -105,12 +107,65 @@ FocusScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// zoom label
|
// Image Metadata overlay Pane
|
||||||
Label {
|
ImageMetadataView {
|
||||||
|
width: 350
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
right: parent.right
|
||||||
|
bottom: bottomToolbar.top
|
||||||
|
margins: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: metadataCB.checked
|
||||||
|
// only load metadata model if visible
|
||||||
|
metadata: visible ? root.metadata : {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pane {
|
||||||
|
id: bottomToolbar
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
width: parent.width
|
||||||
anchors.margins: 4
|
padding: 2
|
||||||
text: (image.status == Image.Ready ? image.scale.toFixed(2) : "1.00") + "x"
|
leftPadding: 4
|
||||||
state: "xsmall"
|
rightPadding: leftPadding
|
||||||
|
|
||||||
|
background: Rectangle { color: palette.base; opacity: 0.6 }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// zoom label
|
||||||
|
Label {
|
||||||
|
text: (image.status == Image.Ready ? image.scale.toFixed(2) : "1.00") + "x"
|
||||||
|
state: "xsmall"
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Label {
|
||||||
|
id: resolutionLabel
|
||||||
|
text: image.sourceSize.width + "x" + image.sourceSize.height
|
||||||
|
anchors.centerIn: parent
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module Viewer
|
module Viewer
|
||||||
|
|
||||||
Viewer2D 1.0 Viewer2D.qml
|
Viewer2D 1.0 Viewer2D.qml
|
||||||
|
ImageMetadataView 1.0 ImageMetadataView.qml
|
||||||
Viewer3D 1.0 Viewer3D.qml
|
Viewer3D 1.0 Viewer3D.qml
|
||||||
DefaultCameraController 1.0 DefaultCameraController.qml
|
DefaultCameraController 1.0 DefaultCameraController.qml
|
||||||
MayaCameraController 1.0 MayaCameraController.qml
|
MayaCameraController 1.0 MayaCameraController.qml
|
||||||
|
|
|
@ -91,11 +91,21 @@ Item {
|
||||||
Viewer2D {
|
Viewer2D {
|
||||||
id: viewer2D
|
id: viewer2D
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
property url imageGallerySource: imageGallery.currentItemSource
|
|
||||||
onImageGallerySourceChanged: viewer2D.source = imageGallerySource
|
Connections {
|
||||||
|
target: imageGallery
|
||||||
|
onCurrentItemChanged: {
|
||||||
|
viewer2D.source = imageGallery.currentItemSource
|
||||||
|
viewer2D.metadata = imageGallery.currentItemMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DropArea {
|
DropArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onDropped: viewer2D.source = drop.urls[0]
|
onDropped: {
|
||||||
|
viewer2D.source = drop.urls[0]
|
||||||
|
viewer2D.metadata = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
z: -1
|
z: -1
|
||||||
|
|
Loading…
Add table
Reference in a new issue