Skip to content

Commit

Permalink
Merge pull request #1989 from alicevision/mug/sequencePlayer
Browse files Browse the repository at this point in the history
[ui] 2D viewer: image sequence player
  • Loading branch information
cbentejac committed Jul 12, 2023
2 parents f70fa1f + 7e85915 commit e0a3944
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 56 deletions.
5 changes: 2 additions & 3 deletions meshroom/ui/qml/Viewer/FloatImage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ AliceVision.FloatImageViewer {

width: textureSize.width
height: textureSize.height
visible: (status === Image.Ready)
visible: true

// paintedWidth / paintedHeight / status for compatibility with standard Image
property int paintedWidth: textureSize.width
Expand Down Expand Up @@ -62,10 +62,9 @@ AliceVision.FloatImageViewer {

property int pointsNumber: (surface.subdivisions + 1) * (surface.subdivisions + 1);

property int index: 0;
property int idView: 0;

clearBeforeLoad: true
clearBeforeLoad: false

property alias containsMouse: mouseArea.containsMouse
property alias mouseX: mouseArea.mouseX
Expand Down
4 changes: 2 additions & 2 deletions meshroom/ui/qml/Viewer/PanoramaViewer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,15 @@ AliceVision.PanoramaViewer {
'surface.pitch': Qt.binding(function() { return root.pitch; }),
'surface.yaw': Qt.binding(function() { return root.yaw; }),
'surface.roll': Qt.binding(function() { return root.roll; }),
'index' : index,
'idView': Qt.binding(function() { return idViewItem; }),
'gamma': Qt.binding(function() { return hdrImageToolbar.gammaValue; }),
'gain': Qt.binding(function() { return hdrImageToolbar.gainValue; }),
'channelModeString': Qt.binding(function() { return hdrImageToolbar.channelModeValue; }),
'downscaleLevel' : Qt.binding(function() { return downscale; }),
'source': Qt.binding(function() { return sourceItem; }),
'surface.msfmData': Qt.binding(function() { return root.msfmData }),
'canBeHovered': true
'canBeHovered': true,
'useSequence': false
})
imageLoaded = Qt.binding(function() { return repeater.itemAt(index).item.status === Image.Ready ? true : false; })
}
Expand Down
262 changes: 262 additions & 0 deletions meshroom/ui/qml/Viewer/SequencePlayer.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.11

import Controls 1.0
import MaterialIcons 2.2
import Utils 1.0

/**
* The Sequence Player is a UI for manipulating
* the currently selected (and displayed) viewpoint
* in an ordered set of viewpoints (i.e. a sequence).
*
* The viewpoint manipulation process can be manual
* (for example by dragging a slider to change the current frame)
* or automatic
* (by playing the sequence, i.e. incrementing the current frame at a given time rate).
*/
FloatingPane {
id: root

// Exposed properties
property var sortedViewIds: []
property var viewer: null

function updateReconstructionView() {
if (_reconstruction && m.frame >= 0 && m.frame < sortedViewIds.length) {
if (m.syncSelected) {
_reconstruction.selectedViewId = sortedViewIds[m.frame];
} else {
_reconstruction.pickedViewId = sortedViewIds[m.frame];
}
}
}

// Sequence player model:
// - current frame
// - data related to automatic sequence playing
QtObject {
id: m

property int frame: 0
property bool syncSelected: true
property bool playing: false
property bool repeat: false
property real fps: 1

onFrameChanged: {
updateReconstructionView();
}

onSyncSelectedChanged: {
updateReconstructionView();
}

onPlayingChanged: {
syncSelected = !playing;
}
}

// Update the frame property
// when the selected view ID is changed externally
Connections {
target: _reconstruction
function onSelectedViewIdChanged() {
for (let idx = 0; idx < sortedViewIds.length; idx++) {
if (_reconstruction.selectedViewId === sortedViewIds[idx] && (m.frame != idx)) {
m.frame = idx;
}
}
}
}

// In play mode
// we use a timer to increment the frame property
// at a given time rate (defined by the fps property)
Timer {
id: timer

repeat: true
running: m.playing
interval: 1000 / m.fps

onTriggered: {
let nextIndex = m.frame + 1;
if (nextIndex == sortedViewIds.length) {
if (m.repeat) {
m.frame = 0;
return;
}
else {
m.playing = false;
return;
}
}
m.frame = nextIndex;
}
}

// Widgets:
// - "Previous Frame" button
// - "Play - Pause" button
// - "Next Frame" button
// - frame label
// - frame slider
// - FPS spin box
// - "Repeat" button
RowLayout {

anchors.fill: parent

MaterialToolButton {
id: prevButton

text: MaterialIcons.skip_previous
ToolTip.text: "Previous Frame"

onClicked: {
m.frame -= 1;
}
}

MaterialToolButton {
id: playButton

checkable: true
checked: false
text: checked ? MaterialIcons.pause : MaterialIcons.play_arrow
ToolTip.text: checked ? "Pause Player" : "Play Sequence"

onCheckedChanged: {
m.playing = checked;
}

Connections {
target: m
function onPlayingChanged() {
playButton.checked = m.playing;
}
}
}

MaterialToolButton {
id: nextButton

text: MaterialIcons.skip_next
ToolTip.text: "Next Frame"

onClicked: {
m.frame += 1;
}
}

Label {
id: frameLabel

text: m.frame
Layout.preferredWidth: frameMetrics.width
}

Slider {
id: frameSlider

Layout.fillWidth: true

stepSize: 1
snapMode: Slider.SnapAlways
live: true

from: 0
to: sortedViewIds.length - 1

onValueChanged: {
m.frame = value;
}

onPressedChanged: {
m.syncSelected = !pressed;
}

Connections {
target: m
function onFrameChanged() {
frameSlider.value = m.frame;
}
}

background: Rectangle {
x: frameSlider.leftPadding
y: frameSlider.topPadding + frameSlider.height / 2 - height / 2
width: frameSlider.availableWidth
height: 4
radius: 2
color: Colors.grey

Repeater {
id: cacheView

model: viewer ? viewer.cachedFrames : []
property real frameLength: sortedViewIds.length > 0 ? frameSlider.width / sortedViewIds.length : 0

Rectangle {
x: modelData.x * cacheView.frameLength
y: 0
width: cacheView.frameLength * (modelData.y - modelData.x + 1)
height: 4
radius: 2
color: Colors.blue
}
}
}
}

RowLayout {
Label {
text: "FPS:"
ToolTip.text: "Frame Per Second"
}

SpinBox {
id: fpsSpinBox

Layout.preferredWidth: fpsMetrics.width + up.implicitIndicatorWidth

from: 1
to: 60
stepSize: 1

onValueChanged: {
m.fps = value;
}
}
}

MaterialToolButton {
id: repeatButton

checkable: true
checked: false
text: MaterialIcons.replay
ToolTip.text: "Repeat"

onCheckedChanged: {
m.repeat = checked;
}
}
}

TextMetrics {
id: frameMetrics

font: frameLabel.font
text: "10000"
}

TextMetrics {
id: fpsMetrics

font: fpsSpinBox.font
text: "100"
}
}
Loading

0 comments on commit e0a3944

Please sign in to comment.