mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-03 04:06:45 +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 cameraInit
|
||||
readonly property alias currentItem: grid.currentItem
|
||||
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
|
||||
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
|
||||
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.Controls 2.0
|
||||
import QtQuick.Layouts 1.3
|
||||
import MaterialIcons 2.2
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
clip: true
|
||||
property alias source: image.source
|
||||
property var metadata
|
||||
|
||||
// slots
|
||||
Keys.onPressed: {
|
||||
|
@ -81,7 +83,7 @@ FocusScope {
|
|||
property double factor: 1.2
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onPressed: {
|
||||
root.forceActiveFocus();
|
||||
image.forceActiveFocus()
|
||||
if(mouse.button & Qt.MiddleButton)
|
||||
drag.target = image // start drag
|
||||
}
|
||||
|
@ -105,12 +107,65 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
// zoom label
|
||||
Label {
|
||||
// Image Metadata overlay Pane
|
||||
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.left: parent.left
|
||||
anchors.margins: 4
|
||||
text: (image.status == Image.Ready ? image.scale.toFixed(2) : "1.00") + "x"
|
||||
state: "xsmall"
|
||||
width: parent.width
|
||||
padding: 2
|
||||
leftPadding: 4
|
||||
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
|
||||
|
||||
Viewer2D 1.0 Viewer2D.qml
|
||||
ImageMetadataView 1.0 ImageMetadataView.qml
|
||||
Viewer3D 1.0 Viewer3D.qml
|
||||
DefaultCameraController 1.0 DefaultCameraController.qml
|
||||
MayaCameraController 1.0 MayaCameraController.qml
|
||||
|
|
|
@ -91,11 +91,21 @@ Item {
|
|||
Viewer2D {
|
||||
id: viewer2D
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
onDropped: viewer2D.source = drop.urls[0]
|
||||
onDropped: {
|
||||
viewer2D.source = drop.urls[0]
|
||||
viewer2D.metadata = {}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
z: -1
|
||||
|
|
Loading…
Add table
Reference in a new issue