diff --git a/Modules/Core/src/Rendering/mitkVideoRecorder.cpp b/Modules/Core/src/Rendering/mitkVideoRecorder.cpp index 117946b90f..064c0efd5a 100644 --- a/Modules/Core/src/Rendering/mitkVideoRecorder.cpp +++ b/Modules/Core/src/Rendering/mitkVideoRecorder.cpp @@ -1,391 +1,409 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::string mitk::VideoRecorder::GetFileExtension(OutputFormat format) { switch (format) { case OutputFormat::WebM_VP9: return ".webm"; case OutputFormat::MP4_H264: return ".mp4"; default: break; } mitkThrow() << "Unknown output format for video recording."; } namespace { mitk::IPreferences* GetPreferences() { auto* preferencesService = mitk::CoreServices::GetPreferencesService(); return preferencesService->GetSystemPreferences()->Node("org.mitk.views.moviemaker"); } class RecordingSession { public: RecordingSession(vtkRenderWindow* renderWindow, mitk::VideoRecorder::OutputFormat format) : m_FrameDir(mitk::IOUtil::CreateTemporaryDirectory("MITK_RecordingSession_XXXXXX")), m_NumberOfFrames(0) { m_WindowToImageFilter->SetInput(renderWindow); if (mitk::VideoRecorder::OutputFormat::MP4_H264 == format) { // H.264 only supports image dimensions that are a multiple of 2. Resize if necessary. auto* size = renderWindow->GetActualSize(); if (size[0] & 1 || size[1] & 1) { m_ImageResize->SetInputConnection(m_WindowToImageFilter->GetOutputPort()); m_ImageResize->SetOutputDimensions(size[0] & ~1, size[1] & ~1, -1); m_ImageResize->SetInterpolate(0); m_ImageResize->BorderOn(); m_ImageWriter->SetInputConnection(m_ImageResize->GetOutputPort()); return; } } m_ImageWriter->SetInputConnection(m_WindowToImageFilter->GetOutputPort()); } ~RecordingSession() { std::error_code errorCode; std::filesystem::remove_all(m_FrameDir, errorCode); } RecordingSession(const RecordingSession&) = delete; RecordingSession& operator=(const RecordingSession&) = delete; std::filesystem::path GetFrameDir() const { return m_FrameDir; } void RecordFrame() { m_WindowToImageFilter->Modified(); std::stringstream frameFilename; frameFilename << std::setw(6) << std::setfill('0') << m_NumberOfFrames << ".png"; const auto framePath = m_FrameDir / frameFilename.str(); m_ImageWriter->SetFileName(framePath.string().c_str()); m_ImageWriter->Write(); ++m_NumberOfFrames; } private: std::filesystem::path m_FrameDir; unsigned int m_NumberOfFrames; vtkNew m_WindowToImageFilter; vtkNew m_ImageResize; vtkNew m_ImageWriter; }; } namespace mitk { class VideoRecorder::Impl { public: Impl() : m_FrameRate(30) { } ~Impl() = default; Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; std::filesystem::path GetFFmpegPath() const { if (m_FFmpegPath) return m_FFmpegPath.value(); auto* preferences = GetPreferences(); if (nullptr != preferences) { auto ffmpegPath = preferences->Get("ffmpeg", ""); if (!ffmpegPath.empty()) return ffmpegPath; } return std::filesystem::path(); } void SetFFmpegPath(const std::filesystem::path& path) { m_FFmpegPath = path; } std::filesystem::path GetOutputPath() const { return m_OutputPath; } void SetOutputPath(const std::filesystem::path& path) { m_OutputPath = path; } mitk::VideoRecorder::OutputFormat GetOutputFormat() const { if (m_OutputFormat) return m_OutputFormat.value(); auto* preferences = GetPreferences(); if (nullptr != preferences) return static_cast(preferences->GetInt("format", 0)); return OutputFormat::WebM_VP9; } void SetOutputFormat(OutputFormat format) { m_OutputFormat = format; } std::string GetRenderWindowName() const { return m_RenderWindowName; } void SetRenderWindowName(const std::string& renderWindowName) { m_RenderWindowName = renderWindowName; } int GetFrameRate() const { return m_FrameRate; } void SetFrameRate(unsigned int fps) { m_FrameRate = fps; } bool OnAir() const { return nullptr != m_RecordingSession.get(); } void StartRecording() { if (this->OnAir()) mitkThrow() << "Recording session already running."; auto renderWindowName = this->GetRenderWindowName(); if (renderWindowName.empty()) mitkThrow() << "No render window specified for recording."; auto* renderWindow = BaseRenderer::GetRenderWindowByName(renderWindowName); if (nullptr == renderWindow) mitkThrow() << "\"" << renderWindowName << "\" references unknown render window for recording."; m_RecordingSession = std::make_unique(renderWindow, this->GetOutputFormat()); } void RecordFrame() { if (!this->OnAir()) mitkThrow() << "Cannot record frame. No recording session running."; m_RecordingSession->RecordFrame(); } std::string GetFFmpegCommandLine() const { bool vp9 = OutputFormat::WebM_VP9 == this->GetOutputFormat(); std::stringstream stream; - stream << this->GetFFmpegPath() + stream << this->GetFFmpegPath() << ' ' << "-y" << ' ' << "-r " << std::to_string(this->GetFrameRate()) << ' ' << "-i %6d.png" << ' ' << "-c:v " << (vp9 ? "libvpx-vp9" : "libx264") << ' ' << "-crf " << (vp9 ? "31" : "23") << ' ' << "-pix_fmt yuv420p" << ' ' << "-b:v 0" << ' ' << this->GetOutputPath(); return stream.str(); } int ExecuteFFmpeg() const { auto commandLine = this->GetFFmpegCommandLine(); auto commandLineCStr = commandLine.c_str(); auto workingDirectory = m_RecordingSession->GetFrameDir().string(); auto* ffmpeg = itksysProcess_New(); itksysProcess_SetOption(ffmpeg, itksysProcess_Option_Verbatim, 1); itksysProcess_SetCommand(ffmpeg, &commandLineCStr); itksysProcess_SetWorkingDirectory(ffmpeg, workingDirectory.c_str()); itksysProcess_Execute(ffmpeg); itksysProcess_WaitForExit(ffmpeg, nullptr); - if (itksysProcess_State_Exited != itksysProcess_GetState(ffmpeg)) + auto state = itksysProcess_GetState(ffmpeg); + + if (itksysProcess_State_Exited != state) { + std::stringstream message; + message << "FFmpeg process did not exit as expected: "; + + if (itksysProcess_State_Error == state) + { + message << itksysProcess_GetErrorString(ffmpeg); + } + else if (itksysProcess_State_Exception == state) + { + message << itksysProcess_GetExceptionString(ffmpeg); + } + + message << "\n Command: " << commandLineCStr; + message << "\n Working directory: " << workingDirectory.c_str(); + itksysProcess_Delete(ffmpeg); - mitkThrow() << "FFmpeg process did not exit as expected."; + + mitkThrow() << message.str(); } auto exitCode = itksysProcess_GetExitValue(ffmpeg); itksysProcess_Delete(ffmpeg); return exitCode; } int StopRecording() { if (!this->OnAir()) mitkThrow() << "No recording session running."; if (this->GetFFmpegPath().empty()) mitkThrow() << "Path to FFmpeg not set."; if (this->GetOutputPath().empty()) mitkThrow() << "Path to output video file not set."; auto exitCode = this->ExecuteFFmpeg(); m_RecordingSession = nullptr; return exitCode; } private: std::optional m_FFmpegPath; std::filesystem::path m_OutputPath; std::optional m_OutputFormat; std::string m_RenderWindowName; unsigned int m_FrameRate; std::unique_ptr m_RecordingSession; }; } mitk::VideoRecorder::VideoRecorder() : m_Impl(std::make_unique()) { } mitk::VideoRecorder::~VideoRecorder() { } std::filesystem::path mitk::VideoRecorder::GetFFmpegPath() const { return m_Impl->GetFFmpegPath(); } void mitk::VideoRecorder::SetFFmpegPath(const std::filesystem::path& path) { m_Impl->SetFFmpegPath(path); } std::filesystem::path mitk::VideoRecorder::GetOutputPath() const { return m_Impl->GetOutputPath(); } void mitk::VideoRecorder::SetOutputPath(const std::filesystem::path& path) { m_Impl->SetOutputPath(path); } mitk::VideoRecorder::OutputFormat mitk::VideoRecorder::GetOutputFormat() const { return m_Impl->GetOutputFormat(); } void mitk::VideoRecorder::SetOutputFormat(OutputFormat format) { m_Impl->SetOutputFormat(format); } std::string mitk::VideoRecorder::GetRenderWindowName() const { return m_Impl->GetRenderWindowName(); } void mitk::VideoRecorder::SetRenderWindowName(const std::string& renderWindowName) { m_Impl->SetRenderWindowName(renderWindowName); } int mitk::VideoRecorder::GetFrameRate() const { return m_Impl->GetFrameRate(); } void mitk::VideoRecorder::SetFrameRate(unsigned int fps) { m_Impl->SetFrameRate(fps); } void mitk::VideoRecorder::StartRecording() { m_Impl->StartRecording(); } void mitk::VideoRecorder::RecordFrame() const { m_Impl->RecordFrame(); } int mitk::VideoRecorder::StopRecording() { return m_Impl->StopRecording(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui index b408230b03..00fe89030a 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspectorControls.ui @@ -1,98 +1,95 @@ QmitkMultiLabelInspector 0 0 400 - 120 + 200 0 0 Form - - 0 - 0 0 0 0 0 0 0 - 120 + 200 16777215 - 120 + 200 QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked false false QAbstractItemView::SingleSelection QAbstractItemView::SelectRows false 10 50 false QmitkMultiLabelTreeView QTreeView
QmitkMultiLabelTreeView.h
diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp index 4b97be0319..55d3bc0f52 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp @@ -1,698 +1,705 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMovieMakerView.h" #include #include "QmitkAnimationItemDelegate.h" #include "QmitkOrbitAnimationItem.h" #include "QmitkOrbitAnimationWidget.h" #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" #include "QmitkTimeSliceAnimationItem.h" #include "QmitkTimeSliceAnimationWidget.h" #include #include #include #include #include #include #include namespace { QmitkAnimationItem* CreateDefaultAnimation(const QString& widgetKey) { if (widgetKey == "Orbit") return new QmitkOrbitAnimationItem; if (widgetKey == "Slice") return new QmitkSliceAnimationItem; if (widgetKey == "Time") return new QmitkTimeSliceAnimationItem; return nullptr; } class EncodingThread : public QThread { public: EncodingThread(mitk::VideoRecorder* videoRecorder, QObject* parent = nullptr) : QThread(parent), m_VideoRecorder(videoRecorder) { } ~EncodingThread() override = default; private: void run() override { - m_VideoRecorder->StopRecording(); + try + { + m_VideoRecorder->StopRecording(); + } + catch (const mitk::Exception& e) + { + MITK_ERROR << e.GetDescription(); + } } mitk::VideoRecorder* m_VideoRecorder; }; } const std::string QmitkMovieMakerView::VIEW_ID = "org.mitk.views.moviemaker"; QmitkMovieMakerView::QmitkMovieMakerView() : m_Parent(nullptr), m_Ui(new Ui::QmitkMovieMakerView), m_AnimationModel(nullptr), m_AddAnimationMenu(nullptr), m_RecordMenu(nullptr), m_Timer(nullptr), m_TotalDuration(0.0), m_NumFrames(0), m_CurrentFrame(0) { } QmitkMovieMakerView::~QmitkMovieMakerView() { } void QmitkMovieMakerView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Ui->setupUi(parent); this->InitializeAnimationWidgets(); this->InitializeAnimationTreeViewWidgets(); this->InitializePlaybackAndRecordWidgets(); this->InitializeTimer(parent); m_Ui->animationWidgetGroupBox->setVisible(false); } void QmitkMovieMakerView::InitializeAnimationWidgets() { m_AnimationWidgets["Orbit"] = new QmitkOrbitAnimationWidget; m_AnimationWidgets["Slice"] = new QmitkSliceAnimationWidget; m_AnimationWidgets["Time"] = new QmitkTimeSliceAnimationWidget; for (const auto& widget : m_AnimationWidgets) { if (nullptr != widget.second) { widget.second->setVisible(false); m_Ui->animationWidgetGroupBoxLayout->addWidget(widget.second); } } this->ConnectAnimationWidgets(); } void QmitkMovieMakerView::InitializeAnimationTreeViewWidgets() { this->InitializeAnimationModel(); this->InitializeAddAnimationMenu(); this->ConnectAnimationTreeViewWidgets(); } void QmitkMovieMakerView::InitializePlaybackAndRecordWidgets() { this->InitializeRecordMenu(); this->ConnectPlaybackAndRecordWidgets(); this->InitializeRecordingProgress(); } void QmitkMovieMakerView::InitializeAnimationModel() { m_AnimationModel = new QStandardItemModel(m_Ui->animationTreeView); m_AnimationModel->setHorizontalHeaderLabels(QStringList() << "Animation" << "Timeline"); m_Ui->animationTreeView->setModel(m_AnimationModel); m_Ui->animationTreeView->setItemDelegate(new QmitkAnimationItemDelegate(m_Ui->animationTreeView)); } void QmitkMovieMakerView::InitializeAddAnimationMenu() { m_AddAnimationMenu = new QMenu(m_Ui->addAnimationButton); for(const auto& widget : m_AnimationWidgets) m_AddAnimationMenu->addAction(widget.first); } void QmitkMovieMakerView::InitializeRecordMenu() { std::array, 4> renderWindows = { std::make_pair(QStringLiteral("Axial"), QStringLiteral("stdmulti.widget0")), std::make_pair(QStringLiteral("Sagittal"), QStringLiteral("stdmulti.widget1")), std::make_pair(QStringLiteral("Coronal"), QStringLiteral("stdmulti.widget2")), std::make_pair(QStringLiteral("3D"), QStringLiteral("stdmulti.widget3")) }; m_RecordMenu = new QMenu(m_Ui->recordButton); for(const auto& renderWindow : renderWindows) { auto* action = new QAction(m_RecordMenu); action->setText(renderWindow.first); action->setData(renderWindow.second); m_RecordMenu->addAction(action); } } void QmitkMovieMakerView::InitializeRecordingProgress() { m_Ui->recordingLabel->setEnabled(true); m_Ui->recordingLabel->setVisible(false); m_Ui->recordingProgressBar->setEnabled(true); m_Ui->recordingProgressBar->setValue(0); m_Ui->recordingProgressBar->setVisible(false); m_Ui->encodingLabel->setEnabled(false); m_Ui->encodingLabel->setVisible(false); m_Ui->encodingProgressBar->setEnabled(false); m_Ui->encodingProgressBar->setMaximum(1); m_Ui->encodingProgressBar->setVisible(false); } void QmitkMovieMakerView::InitializeTimer(QWidget* parent) { m_Timer = new QTimer(parent); this->OnFPSSpinBoxValueChanged(m_Ui->fpsSpinBox->value()); this->ConnectTimer(); } void QmitkMovieMakerView::ConnectAnimationTreeViewWidgets() { connect(m_AnimationModel, &QStandardItemModel::rowsInserted, this, &QmitkMovieMakerView::OnAnimationTreeViewRowsInserted); connect(m_AnimationModel, &QStandardItemModel::rowsRemoved, this, &QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved); connect(m_Ui->animationTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged); connect(m_Ui->moveAnimationUpButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnMoveAnimationUpButtonClicked); connect(m_Ui->moveAnimationDownButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnMoveAnimationDownButtonClicked); connect(m_Ui->addAnimationButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnAddAnimationButtonClicked); connect(m_Ui->removeAnimationButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnRemoveAnimationButtonClicked); } void QmitkMovieMakerView::ConnectAnimationWidgets() { connect(m_Ui->startComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnStartComboBoxCurrentIndexChanged(int))); connect(m_Ui->durationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDurationSpinBoxValueChanged(double))); connect(m_Ui->delaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDelaySpinBoxValueChanged(double))); } void QmitkMovieMakerView::ConnectPlaybackAndRecordWidgets() { connect(m_Ui->playButton, &QToolButton::toggled, this, &QmitkMovieMakerView::OnPlayButtonToggled); connect(m_Ui->stopButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnStopButtonClicked); connect(m_Ui->recordButton, &QToolButton::clicked, this, &QmitkMovieMakerView::OnRecordButtonClicked); connect(m_Ui->fpsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnFPSSpinBoxValueChanged(int))); } void QmitkMovieMakerView::ConnectTimer() { connect(m_Timer, &QTimer::timeout, this, &QmitkMovieMakerView::OnTimerTimeout); } void QmitkMovieMakerView::SetFocus() { m_Ui->addAnimationButton->setFocus(); } void QmitkMovieMakerView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { auto multiWidgetEditor = dynamic_cast(renderWindowPart); bool isMxN = nullptr != multiWidgetEditor && multiWidgetEditor->GetClassName() == "QmitkMxNMultiWidgetEditor"; m_Parent->setDisabled(isMxN); } void QmitkMovieMakerView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { } void QmitkMovieMakerView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { } void QmitkMovieMakerView::OnMoveAnimationUpButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int selectedRow = selection[0].top(); if (selectedRow > 0) m_AnimationModel->insertRow(selectedRow - 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnMoveAnimationDownButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); if (selectedRow < rowCount - 1) m_AnimationModel->insertRow(selectedRow + 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnAddAnimationButtonClicked() { auto action = m_AddAnimationMenu->exec(QCursor::pos()); if (nullptr != action) { const auto key = action->text(); m_AnimationModel->appendRow(QList() << new QStandardItem(key) << CreateDefaultAnimation(key)); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); } } void QmitkMovieMakerView::OnPlayButtonToggled(bool checked) { if (checked) { m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-pause.svg")); m_Ui->playButton->repaint(); m_Timer->start(static_cast(1000.0 / m_Ui->fpsSpinBox->value())); } else { m_Timer->stop(); m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Ui->playButton->repaint(); } } void QmitkMovieMakerView::OnStopButtonClicked() { m_Ui->playButton->setChecked(false); m_Ui->stopButton->setEnabled(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } void QmitkMovieMakerView::OnRecordButtonClicked() { if (0 == m_NumFrames || 0.0 == m_TotalDuration) return; m_VideoRecorder = std::make_unique(); if (m_VideoRecorder->GetFFmpegPath().empty()) { QMessageBox::information(nullptr, "Movie Maker", "

Set path to FFmpeg (ffmpeg.org) in preferences " "(Window -> Preferences... (Ctrl+P) -> Movie Maker) to be able to record your " "movies to video files.

"); return; } auto action = m_RecordMenu->exec(QCursor::pos()); if (nullptr == action) return; m_VideoRecorder->SetRenderWindowName(action->data().toString().toStdString()); m_VideoRecorder->SetFrameRate(static_cast(m_Ui->fpsSpinBox->value())); auto fileExt = QString::fromStdString(mitk::VideoRecorder::GetFileExtension(m_VideoRecorder->GetOutputFormat())); QString outputPath = QFileDialog::getSaveFileName(nullptr, "Specify a filename", "", "Movie (*" + fileExt + ")"); if (outputPath.isEmpty()) return; if(!outputPath.endsWith(fileExt)) outputPath += fileExt; m_VideoRecorder->SetOutputPath(outputPath.toStdString()); m_Ui->recordButton->setEnabled(false); m_Ui->recordingProgressBar->setMaximum(m_NumFrames); m_Ui->recordingLabel->setVisible(true); m_Ui->recordingProgressBar->setVisible(true); m_Ui->encodingLabel->setVisible(true); m_Ui->encodingProgressBar->setVisible(true); EncodingThread* encodingThread = new EncodingThread(m_VideoRecorder.get(), m_Parent); connect(encodingThread, &EncodingThread::finished, this, &QmitkMovieMakerView::OnEncodingFinished); try { m_VideoRecorder->StartRecording(); for (m_CurrentFrame = 0; m_CurrentFrame < m_NumFrames; ++m_CurrentFrame) { m_Ui->recordingProgressBar->setValue(m_CurrentFrame + 1); this->RenderCurrentFrame(); m_VideoRecorder->RecordFrame(); } m_Ui->encodingProgressBar->setMaximum(0); m_Ui->encodingLabel->setEnabled(true); m_Ui->encodingProgressBar->setEnabled(true); encodingThread->start(); } catch (const mitk::Exception& exception) { if (encodingThread->isRunning()) encodingThread->terminate(); QMessageBox::critical(nullptr, "Movie Maker", exception.GetDescription()); m_VideoRecorder = nullptr; this->OnEncodingFinished(); } } void QmitkMovieMakerView::OnEncodingFinished() { m_VideoRecorder = nullptr; this->InitializeRecordingProgress(); m_CurrentFrame = 0; this->RenderCurrentFrame(); m_Ui->recordButton->setEnabled(true); } void QmitkMovieMakerView::OnRemoveAnimationButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) m_AnimationModel->removeRow(selection[0].top()); } void QmitkMovieMakerView::OnAnimationTreeViewRowsInserted(const QModelIndex& parent, int start, int) { this->CalculateTotalDuration(); m_Ui->animationTreeView->selectionModel()->select( m_AnimationModel->index(start, 0, parent), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int) { this->CalculateTotalDuration(); this->UpdateWidgets(); } void QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&) { this->UpdateWidgets(); } void QmitkMovieMakerView::OnStartComboBoxCurrentIndexChanged(int index) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetStartWithPrevious(index); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDurationSpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetDuration(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDelaySpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { item->SetDelay(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnFPSSpinBoxValueChanged(int value) { this->CalculateTotalDuration(); m_Timer->setInterval(static_cast(1000.0 / value)); } void QmitkMovieMakerView::OnTimerTimeout() { this->RenderCurrentFrame(); m_CurrentFrame = std::min(m_NumFrames, m_CurrentFrame + 1); if (m_CurrentFrame >= m_NumFrames) { m_Ui->playButton->setChecked(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } m_Ui->stopButton->setEnabled(m_CurrentFrame != 0); } void QmitkMovieMakerView::RenderCurrentFrame() { const double deltaT = m_TotalDuration / (m_NumFrames - 1); const auto activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); for (const auto& animation : activeAnimations) { const auto nextActiveAnimations = this->GetActiveAnimations((m_CurrentFrame + 1) * deltaT); bool lastFrameForAnimation = true; for (const auto& nextAnimation : nextActiveAnimations) { if (nextAnimation.first == animation.first) { lastFrameForAnimation = false; break; } } animation.first->Animate(!lastFrameForAnimation ? animation.second : 1.0); } mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void QmitkMovieMakerView::UpdateWidgets() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (selection.isEmpty()) { m_Ui->moveAnimationUpButton->setEnabled(false); m_Ui->moveAnimationDownButton->setEnabled(false); m_Ui->removeAnimationButton->setEnabled(false); m_Ui->playbackAndRecordingGroupBox->setEnabled(false); this->HideCurrentAnimationWidget(); } else { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); m_Ui->moveAnimationUpButton->setEnabled(rowCount > 1 && selectedRow != 0); m_Ui->moveAnimationDownButton->setEnabled(rowCount > 1 && selectedRow < rowCount - 1); m_Ui->removeAnimationButton->setEnabled(true); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); this->ShowAnimationWidget(dynamic_cast(m_AnimationModel->item(selectedRow, 1))); } this->UpdateAnimationWidgets(); } void QmitkMovieMakerView::UpdateAnimationWidgets() { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != nullptr) { m_Ui->startComboBox->setCurrentIndex(item->GetStartWithPrevious()); m_Ui->durationSpinBox->setValue(item->GetDuration()); m_Ui->delaySpinBox->setValue(item->GetDelay()); m_Ui->animationGroupBox->setEnabled(true); } else { m_Ui->animationGroupBox->setEnabled(false); } } void QmitkMovieMakerView::HideCurrentAnimationWidget() { if (m_Ui->animationWidgetGroupBox->isVisible()) { m_Ui->animationWidgetGroupBox->setVisible(false); int numWidgets = m_Ui->animationWidgetGroupBoxLayout->count(); for (int i = 0; i < numWidgets; ++i) m_Ui->animationWidgetGroupBoxLayout->itemAt(i)->widget()->setVisible(false); } } void QmitkMovieMakerView::ShowAnimationWidget(QmitkAnimationItem* animationItem) { this->HideCurrentAnimationWidget(); if (animationItem == nullptr) return; const QString widgetKey = animationItem->GetWidgetKey(); auto animationWidgetIter = m_AnimationWidgets.find(widgetKey); auto animationWidget = m_AnimationWidgets.end() != animationWidgetIter ? animationWidgetIter->second : nullptr; if (nullptr != animationWidget) { m_Ui->animationWidgetGroupBox->setTitle(widgetKey); animationWidget->SetAnimationItem(animationItem); animationWidget->setVisible(true); } m_Ui->animationWidgetGroupBox->setVisible(animationWidget != nullptr); } void QmitkMovieMakerView::RedrawTimeline() { if (m_AnimationModel->rowCount() > 1) { m_Ui->animationTreeView->dataChanged( m_AnimationModel->index(0, 1), m_AnimationModel->index(m_AnimationModel->rowCount() - 1, 1)); } } QmitkAnimationItem* QmitkMovieMakerView::GetSelectedAnimationItem() const { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); return !selection.isEmpty() ? dynamic_cast(m_AnimationModel->item(selection[0].top(), 1)) : nullptr; } void QmitkMovieMakerView::CalculateTotalDuration() { const int rowCount = m_AnimationModel->rowCount(); double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { auto item = dynamic_cast(m_AnimationModel->item(i, 1)); if (nullptr == item) continue; if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } m_TotalDuration = totalDuration; m_NumFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); } std::vector> QmitkMovieMakerView::GetActiveAnimations(double t) const { const int rowCount = m_AnimationModel->rowCount(); std::vector> activeAnimations; double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { QmitkAnimationItem* item = dynamic_cast(m_AnimationModel->item(i, 1)); if (item == nullptr) continue; if (item->GetDuration() > 0.0) { double start = item->GetStartWithPrevious() ? previousStart + item->GetDelay() : totalDuration + item->GetDelay(); if (start <= t && t <= start + item->GetDuration()) activeAnimations.emplace_back(item, (t - start) / item->GetDuration()); } if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } return activeAnimations; }