diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.cpp b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.cpp index f985bfd402..ae332ba296 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.cpp @@ -1,164 +1,176 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkFFmpegWriter.h" #include #include #include QmitkFFmpegWriter::QmitkFFmpegWriter(QObject* parent) : QObject(parent), m_Process(new QProcess(this)), - m_Framerate(0) + m_Framerate(0), + m_IsRunning(false) { this->connect(m_Process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(OnProcessError(QProcess::ProcessError))); this->connect(m_Process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(OnProcessFinished(int, QProcess::ExitStatus))); } QmitkFFmpegWriter::~QmitkFFmpegWriter() { } QString QmitkFFmpegWriter::GetFFmpegPath() const { return m_FFmpegPath; } void QmitkFFmpegWriter::SetFFmpegPath(const QString& path) { m_FFmpegPath = path; } QSize QmitkFFmpegWriter::GetSize() const { return m_Size; } void QmitkFFmpegWriter::SetSize(const QSize& size) { m_Size = size; } void QmitkFFmpegWriter::SetSize(int width, int height) { m_Size = QSize(width, height); } int QmitkFFmpegWriter::GetFramerate() const { return m_Framerate; } void QmitkFFmpegWriter::SetFramerate(int framerate) { m_Framerate = framerate; } QString QmitkFFmpegWriter::GetOutputPath() const { return m_OutputPath; } void QmitkFFmpegWriter::SetOutputPath(const QString& path) { m_OutputPath = path; } void QmitkFFmpegWriter::Start() { if (m_FFmpegPath.isEmpty()) mitkThrow() << "FFmpeg/Libav path is empty!"; if (m_Size.isNull()) mitkThrow() << "Invalid video frame size!"; if (m_Framerate <= 0) mitkThrow() << "Invalid framerate!"; if (m_OutputPath.isEmpty()) mitkThrow() << "Output path is empty!"; m_Process->start(m_FFmpegPath, QStringList() << "-y" << "-f" << "rawvideo" << "-pix_fmt" << "rgb24" << "-s" << QString("%1x%2").arg(m_Size.width()).arg(m_Size.height()) << "-r" << QString("%1").arg(m_Framerate) << "-i" << "-" << "-vf" << "vflip" << "-pix_fmt" << "yuv420p" << "-crf" << "18" << m_OutputPath); m_Process->waitForStarted(); + m_IsRunning = true; +} + +bool QmitkFFmpegWriter::IsRunning() const +{ + return m_IsRunning; } void QmitkFFmpegWriter::WriteFrame(const unsigned char* frame) { if (frame == NULL || !m_Process->isOpen()) return; m_Process->write(reinterpret_cast(frame), m_Size.width() * m_Size.height() * 3); m_Process->waitForBytesWritten(); } void QmitkFFmpegWriter::Stop() { + m_IsRunning = false; m_Process->closeWriteChannel(); } void QmitkFFmpegWriter::OnProcessError(QProcess::ProcessError error) { + m_IsRunning = false; + MITK_ERROR << QString::fromLatin1(m_Process->readAllStandardError()).toStdString(); switch (error) { case QProcess::FailedToStart: mitkThrow() << "FFmpeg/Libav failed to start!"; case QProcess::Crashed: mitkThrow() << "FFmpeg/Libav crashed!"; case QProcess::Timedout: mitkThrow() << "FFmpeg/Libav timed out!"; case QProcess::WriteError: mitkThrow() << "Could not write to FFmpeg/Libav!"; case QProcess::ReadError: mitkThrow() << "Could not read from FFmpeg/Libav!"; default: mitkThrow() << "An unknown error occurred!"; } } void QmitkFFmpegWriter::OnProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { + m_IsRunning = false; + if (exitStatus != QProcess::CrashExit) { if (exitCode != 0) { m_Process->close(); mitkThrow() << QString("FFmpeg/Libav exit code: %1").arg(exitCode).toStdString().c_str(); } } m_Process->close(); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.h b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.h index 0380823040..5527edb538 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.h +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkFFmpegWriter.h @@ -1,60 +1,62 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkFFmpegWriter_h #define QmitkFFmpegWriter_h #include #include class QmitkFFmpegWriter : public QObject { Q_OBJECT public: explicit QmitkFFmpegWriter(QObject* parent = NULL); ~QmitkFFmpegWriter(); QString GetFFmpegPath() const; void SetFFmpegPath(const QString& path); QSize GetSize() const; void SetSize(const QSize& size); void SetSize(int width, int height); int GetFramerate() const; void SetFramerate(int framerate); QString GetOutputPath() const; void SetOutputPath(const QString& path); void Start(); + bool IsRunning() const; void WriteFrame(const unsigned char* frame); void Stop(); private slots: void OnProcessError(QProcess::ProcessError error); void OnProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); private: QProcess* m_Process; QString m_FFmpegPath; QSize m_Size; int m_Framerate; QString m_OutputPath; + bool m_IsRunning; }; #endif diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.cpp b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.cpp index 6a4c4c911a..2977e4a432 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.cpp @@ -1,604 +1,664 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkAnimationItemDelegate.h" #include "QmitkFFmpegWriter.h" #include "QmitkMovieMaker2View.h" #include "QmitkOrbitAnimationItem.h" #include "QmitkOrbitAnimationWidget.h" #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" #include #include #include #include #include +#include #include #include static QmitkAnimationItem* CreateDefaultAnimation(const QString& widgetKey) { if (widgetKey == "Orbit") return new QmitkOrbitAnimationItem; if (widgetKey == "Slice") - return new QmitkSliceAnimationItem(0, 0, 999, false); + return new QmitkSliceAnimationItem; return NULL; } static QString GetFFmpegPath() { berry::IPreferencesService::Pointer preferencesService = berry::Platform::GetServiceRegistry().GetServiceById(berry::IPreferencesService::ID); berry::IPreferences::Pointer preferences = preferencesService->GetSystemPreferences()->Node("/org.mitk.gui.qt.ext.externalprograms"); return QString::fromStdString(preferences->Get("ffmpeg", "")); } static unsigned char* ReadPixels(vtkRenderWindow* renderWindow, int x, int y, int width, int height) { if (renderWindow == NULL) return NULL; unsigned char* frame = new unsigned char[width * height * 3]; renderWindow->MakeCurrent(); glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame); return frame; } const std::string QmitkMovieMaker2View::VIEW_ID = "org.mitk.views.moviemaker2"; QmitkMovieMaker2View::QmitkMovieMaker2View() : m_FFmpegWriter(NULL), m_Ui(new Ui::QmitkMovieMaker2View), m_AnimationModel(NULL), m_AddAnimationMenu(NULL), - m_RecordMenu(NULL) + m_RecordMenu(NULL), + m_Timer(NULL), + m_TotalDuration(0.0), + m_NumFrames(0), + m_CurrentFrame(0) { } QmitkMovieMaker2View::~QmitkMovieMaker2View() { } void QmitkMovieMaker2View::CreateQtPartControl(QWidget* parent) { m_FFmpegWriter = new QmitkFFmpegWriter(parent); m_Ui->setupUi(parent); this->InitializeAnimationWidgets(); this->InitializeAnimationTreeViewWidgets(); this->InitializePlaybackAndRecordWidgets(); + this->InitializeTimer(parent); m_Ui->animationWidgetGroupBox->setVisible(false); } void QmitkMovieMaker2View::InitializeAnimationWidgets() { m_AnimationWidgets["Orbit"] = new QmitkOrbitAnimationWidget; m_AnimationWidgets["Slice"] = new QmitkSliceAnimationWidget; Q_FOREACH(QWidget* widget, m_AnimationWidgets.values()) { if (widget != NULL) { widget->setVisible(false); m_Ui->animationWidgetGroupBoxLayout->addWidget(widget); } } this->ConnectAnimationWidgets(); } void QmitkMovieMaker2View::InitializeAnimationTreeViewWidgets() { this->InitializeAnimationModel(); this->InitializeAddAnimationMenu(); this->ConnectAnimationTreeViewWidgets(); } void QmitkMovieMaker2View::InitializePlaybackAndRecordWidgets() { this->InitializeRecordMenu(); this->ConnectPlaybackAndRecordWidgets(); } void QmitkMovieMaker2View::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 QmitkMovieMaker2View::InitializeAddAnimationMenu() { m_AddAnimationMenu = new QMenu(m_Ui->addAnimationButton); Q_FOREACH(const QString& key, m_AnimationWidgets.keys()) { m_AddAnimationMenu->addAction(key); } } void QmitkMovieMaker2View::InitializeRecordMenu() { typedef QPair PairOfStrings; m_RecordMenu = new QMenu(m_Ui->recordButton); QVector renderWindows; renderWindows.push_back(qMakePair(QString("Axial"), QString("stdmulti.widget1"))); renderWindows.push_back(qMakePair(QString("Sagittal"), QString("stdmulti.widget2"))); renderWindows.push_back(qMakePair(QString("Coronal"), QString("stdmulti.widget3"))); renderWindows.push_back(qMakePair(QString("3D"), QString("stdmulti.widget4"))); Q_FOREACH(const PairOfStrings& renderWindow, renderWindows) { QAction* action = new QAction(m_RecordMenu); action->setText(renderWindow.first); action->setData(renderWindow.second); m_RecordMenu->addAction(action); } } +void QmitkMovieMaker2View::InitializeTimer(QWidget* parent) +{ + m_Timer = new QTimer(parent); + + this->OnFPSSpinBoxValueChanged(m_Ui->fpsSpinBox->value()); + this->ConnectTimer(); +} + void QmitkMovieMaker2View::ConnectAnimationTreeViewWidgets() { this->connect(m_AnimationModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(OnAnimationTreeViewRowsInserted(const QModelIndex&, int, int))); this->connect(m_AnimationModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int))); this->connect(m_Ui->animationTreeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&))); this->connect(m_Ui->moveAnimationUpButton, SIGNAL(clicked()), this, SLOT(OnMoveAnimationUpButtonClicked())); this->connect(m_Ui->moveAnimationDownButton, SIGNAL(clicked()), this, SLOT(OnMoveAnimationDownButtonClicked())); this->connect(m_Ui->addAnimationButton, SIGNAL(clicked()), this, SLOT(OnAddAnimationButtonClicked())); this->connect(m_Ui->removeAnimationButton, SIGNAL(clicked()), this, SLOT(OnRemoveAnimationButtonClicked())); } void QmitkMovieMaker2View::ConnectAnimationWidgets() { this->connect(m_Ui->startComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnStartComboBoxCurrentIndexChanged(int))); this->connect(m_Ui->durationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDurationSpinBoxValueChanged(double))); this->connect(m_Ui->delaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDelaySpinBoxValueChanged(double))); } void QmitkMovieMaker2View::ConnectPlaybackAndRecordWidgets() { this->connect(m_Ui->playButton, SIGNAL(toggled(bool)), this, SLOT(OnPlayButtonToggled(bool))); + this->connect(m_Ui->stopButton, SIGNAL(clicked()), + this, SLOT(OnStopButtonClicked())); + this->connect(m_Ui->recordButton, SIGNAL(clicked()), this, SLOT(OnRecordButtonClicked())); + + this->connect(m_Ui->fpsSpinBox, SIGNAL(valueChanged(int)), + this, SLOT(OnFPSSpinBoxValueChanged(int))); +} + +void QmitkMovieMaker2View::ConnectTimer() +{ + this->connect(m_Timer, SIGNAL(timeout()), + this, SLOT(OnTimerTimeout())); } void QmitkMovieMaker2View::SetFocus() { m_Ui->addAnimationButton->setFocus(); } void QmitkMovieMaker2View::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 QmitkMovieMaker2View::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 QmitkMovieMaker2View::OnAddAnimationButtonClicked() { QAction* action = m_AddAnimationMenu->exec(QCursor::pos()); if (action != NULL) { const QString widgetKey = action->text(); m_AnimationModel->appendRow(QList() << new QStandardItem(widgetKey) << CreateDefaultAnimation(widgetKey)); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); } } -void QmitkMovieMaker2View::OnPlayButtonToggled(bool checked) // TODO: Refactor +void QmitkMovieMaker2View::OnPlayButtonToggled(bool checked) { - typedef QPair AnimationIterpolationFactorPair; if (checked) { m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-pause.svg")); m_Ui->playButton->repaint(); - const double totalDuration = this->CalculateTotalDuration(); // TODO totalDuration == 0 - const int numFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); // TODO numFrames < 2 - const double deltaT = totalDuration / (numFrames - 1); - - for (int i = 0; i < numFrames; ++i) - { - QVector activeAnimations = this->GetActiveAnimations(i * deltaT); - - Q_FOREACH(const AnimationIterpolationFactorPair& animation, activeAnimations) - { - animation.first->Animate(animation.second); - } - - mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); - } + 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(); } +} - /*vtkRenderWindow* renderWindow = mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget4"); - mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(renderWindow)->GetCameraRotationController()->GetSlice(); - - unsigned int startPos = stepper->GetPos(); - - for (unsigned int i = 1; i < 30; ++i) - { - unsigned int newPos = startPos + 360.0 / 29.0 * i; - if (newPos > 360) - newPos -= 360; - stepper->SetPos(newPos); +void QmitkMovieMaker2View::OnStopButtonClicked() +{ + m_Ui->playButton->setChecked(false); + m_Ui->stopButton->setEnabled(false); - mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(renderWindow); - }*/ + m_CurrentFrame = 0; + this->RenderCurrentFrame(); } void QmitkMovieMaker2View::OnRecordButtonClicked() // TODO: Refactor { m_FFmpegWriter->SetFFmpegPath(GetFFmpegPath()); if (m_FFmpegWriter->GetFFmpegPath().isEmpty()) { QMessageBox::critical(NULL, "Movie Maker 2", "Path to FFmpeg executable is not set in preferences!"); return; } QAction* action = m_RecordMenu->exec(QCursor::pos()); if (action == NULL) return; vtkRenderWindow* renderWindow = mitk::BaseRenderer::GetRenderWindowByName(action->data().toString().toStdString()); if (renderWindow == NULL) return; const int border = 3; const int x = border; const int y = border; int width = renderWindow->GetSize()[0] - border * 2; int height = renderWindow->GetSize()[1] - border * 2; if (width & 1) --width; if (height & 1) --height; if (width < 16 || height < 16) return; m_FFmpegWriter->SetSize(width, height); m_FFmpegWriter->SetFramerate(m_Ui->fpsSpinBox->value()); QString saveFileName = QFileDialog::getSaveFileName(NULL, "Specify a filename", "", "Movie (*.mp4)"); if (saveFileName.isEmpty()) return; if(!saveFileName.endsWith(".mp4")) saveFileName += ".mp4"; m_FFmpegWriter->SetOutputPath(saveFileName); try { m_FFmpegWriter->Start(); - // TODO: Play animation - - for (int i = 0; i < 30; ++i) + for (m_CurrentFrame = 0; m_CurrentFrame < m_NumFrames; ++m_CurrentFrame) { + this->RenderCurrentFrame(); + renderWindow->MakeCurrent(); unsigned char* frame = ReadPixels(renderWindow, x, y, width, height); m_FFmpegWriter->WriteFrame(frame); delete[] frame; - mitk::BaseRenderer::GetInstance(renderWindow)->GetCameraRotationController()->GetSlice()->Next(); - mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(renderWindow); } m_FFmpegWriter->Stop(); + + m_CurrentFrame = 0; + this->RenderCurrentFrame(); } catch (const mitk::Exception& exception) { + m_CurrentFrame = 0; + this->RenderCurrentFrame(); + QMessageBox::critical(NULL, "Movie Maker 2", exception.GetDescription()); } } void QmitkMovieMaker2View::OnRemoveAnimationButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) m_AnimationModel->removeRow(selection[0].top()); } void QmitkMovieMaker2View::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 QmitkMovieMaker2View::OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int) { + this->CalculateTotalDuration(); this->UpdateWidgets(); } void QmitkMovieMaker2View::OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&) { this->UpdateWidgets(); } void QmitkMovieMaker2View::OnStartComboBoxCurrentIndexChanged(int index) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { item->SetStartWithPrevious(index); this->RedrawTimeline(); + this->CalculateTotalDuration(); } } void QmitkMovieMaker2View::OnDurationSpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { item->SetDuration(value); this->RedrawTimeline(); + } } void QmitkMovieMaker2View::OnDelaySpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { item->SetDelay(value); this->RedrawTimeline(); + this->CalculateTotalDuration(); } } +void QmitkMovieMaker2View::OnFPSSpinBoxValueChanged(int value) +{ + this->CalculateTotalDuration(); + m_Timer->setInterval(static_cast(1000.0 / value)); +} + +void QmitkMovieMaker2View::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 QmitkMovieMaker2View::RenderCurrentFrame() +{ + typedef QPair AnimationIterpolationFactorPair; + + const double deltaT = m_TotalDuration / (m_NumFrames - 1); + const QVector activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); + + Q_FOREACH(const AnimationIterpolationFactorPair& animation, activeAnimations) + { + animation.first->Animate(animation.second); + } + + mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); +} + void QmitkMovieMaker2View::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 QmitkMovieMaker2View::UpdateAnimationWidgets() { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { 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 QmitkMovieMaker2View::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 QmitkMovieMaker2View::ShowAnimationWidget(QmitkAnimationItem* animationItem) { this->HideCurrentAnimationWidget(); if (animationItem == NULL) return; const QString widgetKey = animationItem->GetWidgetKey(); QmitkAnimationWidget* animationWidget = NULL; if (m_AnimationWidgets.contains(widgetKey)) { animationWidget = m_AnimationWidgets[widgetKey]; if (animationWidget != NULL) { m_Ui->animationWidgetGroupBox->setTitle(widgetKey); animationWidget->SetAnimationItem(animationItem); animationWidget->setVisible(true); } } m_Ui->animationWidgetGroupBox->setVisible(animationWidget != NULL); } void QmitkMovieMaker2View::RedrawTimeline() { if (m_AnimationModel->rowCount() > 1) { m_Ui->animationTreeView->dataChanged( m_AnimationModel->index(0, 1), m_AnimationModel->index(m_AnimationModel->rowCount() - 1, 1)); } } QmitkAnimationItem* QmitkMovieMaker2View::GetSelectedAnimationItem() const { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); return !selection.isEmpty() ? dynamic_cast(m_AnimationModel->item(selection[0].top(), 1)) : NULL; } -double QmitkMovieMaker2View::CalculateTotalDuration() const +void QmitkMovieMaker2View::CalculateTotalDuration() { const int rowCount = m_AnimationModel->rowCount(); 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 == NULL) continue; if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } - return totalDuration; + m_TotalDuration = totalDuration; // TODO totalDuration == 0 + m_NumFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); // TODO numFrames < 2 } QVector > QmitkMovieMaker2View::GetActiveAnimations(double t) const { const int rowCount = m_AnimationModel->rowCount(); QVector > 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 == NULL) continue; if (item->GetDuration() > 0.0) { double start = item->GetStartWithPrevious() ? previousStart + item->GetDelay() : totalDuration + item->GetDelay(); if (start <= t && t <= start + item->GetDuration()) activeAnimations.push_back(qMakePair(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; } diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.h b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.h index 4a368dc2ee..50a94a2f9d 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.h +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkMovieMaker2View.h @@ -1,87 +1,98 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkMovieMaker2View_h #define QmitkMovieMaker2View_h #include class QmitkAnimationItem; class QmitkAnimationWidget; class QmitkFFmpegWriter; class QMenu; class QStandardItemModel; +class QTimer; namespace Ui { class QmitkMovieMaker2View; } class QmitkMovieMaker2View : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; QmitkMovieMaker2View(); ~QmitkMovieMaker2View(); void CreateQtPartControl(QWidget* parent); void SetFocus(); private slots: void OnMoveAnimationUpButtonClicked(); void OnMoveAnimationDownButtonClicked(); void OnAddAnimationButtonClicked(); void OnRemoveAnimationButtonClicked(); void OnAnimationTreeViewRowsInserted(const QModelIndex& parent, int start, int end); void OnAnimationTreeViewRowsRemoved(const QModelIndex& parent, int start, int end); void OnAnimationTreeViewSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void OnStartComboBoxCurrentIndexChanged(int index); void OnDurationSpinBoxValueChanged(double value); void OnDelaySpinBoxValueChanged(double value); void OnPlayButtonToggled(bool checked); + void OnStopButtonClicked(); void OnRecordButtonClicked(); + void OnFPSSpinBoxValueChanged(int value); + void OnTimerTimeout(); private: void InitializeAnimationWidgets(); void InitializeAnimationTreeViewWidgets(); void InitializeAnimationModel(); void InitializeAddAnimationMenu(); void InitializePlaybackAndRecordWidgets(); void InitializeRecordMenu(); + void InitializeTimer(QWidget* parent); void ConnectAnimationTreeViewWidgets(); void ConnectAnimationWidgets(); void ConnectPlaybackAndRecordWidgets(); + void ConnectTimer(); + void RenderCurrentFrame(); void UpdateWidgets(); void UpdateAnimationWidgets(); void HideCurrentAnimationWidget(); void ShowAnimationWidget(QmitkAnimationItem* animationItem); void RedrawTimeline(); - double CalculateTotalDuration() const; + void CalculateTotalDuration(); QmitkAnimationItem* GetSelectedAnimationItem() const; QVector > QmitkMovieMaker2View::GetActiveAnimations(double t) const; QmitkFFmpegWriter* m_FFmpegWriter; Ui::QmitkMovieMaker2View* m_Ui; QStandardItemModel* m_AnimationModel; QMap m_AnimationWidgets; QMenu* m_AddAnimationMenu; QMenu* m_RecordMenu; + QTimer* m_Timer; + double m_TotalDuration; + int m_NumFrames; + int m_CurrentFrame; }; #endif diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkOrbitAnimationItem.cpp b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkOrbitAnimationItem.cpp index 314d9fb57e..a65c898f31 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkOrbitAnimationItem.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkOrbitAnimationItem.cpp @@ -1,78 +1,85 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkOrbitAnimationItem.h" #include QmitkOrbitAnimationItem::QmitkOrbitAnimationItem(int startAngle, int orbit, bool reverse, double duration, double delay, bool startWithPrevious) : QmitkAnimationItem("Orbit", duration, delay, startWithPrevious) { this->SetStartAngle(startAngle); this->SetOrbit(orbit); this->SetReverse(reverse); } QmitkOrbitAnimationItem::~QmitkOrbitAnimationItem() { } int QmitkOrbitAnimationItem::GetStartAngle() const { return this->data(StartAngleRole).toInt(); } void QmitkOrbitAnimationItem::SetStartAngle(int angle) { this->setData(angle, StartAngleRole); } int QmitkOrbitAnimationItem::GetOrbit() const { return this->data(OrbitRole).toInt(); } void QmitkOrbitAnimationItem::SetOrbit(int angle) { this->setData(angle, OrbitRole); } bool QmitkOrbitAnimationItem::GetReverse() const { return this->data(ReverseRole).toBool(); } void QmitkOrbitAnimationItem::SetReverse(bool reverse) { this->setData(reverse, ReverseRole); } void QmitkOrbitAnimationItem::Animate(double s) { vtkRenderWindow* renderWindow = mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget4"); + + if (renderWindow == NULL) + return; + mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(renderWindow)->GetCameraRotationController()->GetSlice(); + if (stepper == NULL) + return; + int newPos = this->GetReverse() ? this->GetStartAngle() - this->GetOrbit() * s : this->GetStartAngle() + this->GetOrbit() * s; while (newPos < 0) newPos += 360; while (newPos > 360) newPos -= 360; stepper->SetPos(static_cast(newPos)); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.cpp b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.cpp index 296a4575e6..624512a7ec 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.cpp @@ -1,74 +1,91 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkSliceAnimationItem.h" +#include QmitkSliceAnimationItem::QmitkSliceAnimationItem(int renderWindow, int from, int to, bool reverse, double duration, double delay, bool startWithPrevious) : QmitkAnimationItem("Slice", duration, delay, startWithPrevious) { this->SetRenderWindow(renderWindow); this->SetFrom(from); this->SetTo(to); this->SetReverse(reverse); } QmitkSliceAnimationItem::~QmitkSliceAnimationItem() { } int QmitkSliceAnimationItem::GetRenderWindow() const { return this->data(RenderWindowRole).toInt(); } void QmitkSliceAnimationItem::SetRenderWindow(int renderWindow) { this->setData(renderWindow, RenderWindowRole); } int QmitkSliceAnimationItem::GetFrom() const { return this->data(FromRole).toInt(); } void QmitkSliceAnimationItem::SetFrom(int from) { this->setData(from, FromRole); } int QmitkSliceAnimationItem::GetTo() const { return this->data(ToRole).toInt(); } void QmitkSliceAnimationItem::SetTo(int to) { this->setData(to, ToRole); } bool QmitkSliceAnimationItem::GetReverse() const { return this->data(ReverseRole).toBool(); } void QmitkSliceAnimationItem::SetReverse(bool reverse) { this->setData(reverse, ReverseRole); } -void QmitkSliceAnimationItem::Animate(double /*s*/) +void QmitkSliceAnimationItem::Animate(double s) { + const QString renderWindowName = QString("stdmulti.widget%1").arg(this->GetRenderWindow() + 1); + vtkRenderWindow* renderWindow = mitk::BaseRenderer::GetRenderWindowByName(renderWindowName.toStdString()); + + if (renderWindow == NULL) + return; + + mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(renderWindow)->GetSliceNavigationController()->GetSlice(); + + if (stepper == NULL) + return; + + int newPos = this->GetReverse() + ? this->GetTo() - static_cast((this->GetTo() - this->GetFrom()) * s) + : this->GetFrom() + static_cast((this->GetTo() - this->GetFrom()) * s); + + stepper->SetPos(static_cast(newPos)); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.h b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.h index 74d0d0128d..2626740387 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.h +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationItem.h @@ -1,43 +1,43 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkSliceAnimationItem_h #define QmitkSliceAnimationItem_h #include "QmitkAnimationItem.h" class QmitkSliceAnimationItem : public QmitkAnimationItem { public: - QmitkSliceAnimationItem(int renderWindow, int from, int to, bool reverse, double duration = 2.0, double delay = 0.0, bool startWithPrevious = false); + explicit QmitkSliceAnimationItem(int renderWindow = 0, int from = 0, int to = 0, bool reverse = false, double duration = 2.0, double delay = 0.0, bool startWithPrevious = false); virtual ~QmitkSliceAnimationItem(); int GetRenderWindow() const; void SetRenderWindow(int renderWindow); int GetFrom() const; void SetFrom(int from); int GetTo() const; void SetTo(int to); bool GetReverse() const; void SetReverse(bool reverse); void Animate(double s); }; #endif diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.cpp b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.cpp index 13537054ad..525022fd49 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.cpp @@ -1,94 +1,120 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" +#include #include +static unsigned int GetNumberOfSlices(int renderWindow) +{ + const QString renderWindowName = QString("stdmulti.widget%1").arg(renderWindow + 1); + vtkRenderWindow* theRenderWindow = mitk::BaseRenderer::GetRenderWindowByName(renderWindowName.toStdString()); + + if (theRenderWindow != NULL) + { + mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(theRenderWindow)->GetSliceNavigationController()->GetSlice(); + + if (stepper != NULL) + return std::max(1U, stepper->GetSteps()); + } + + return 1; +} + QmitkSliceAnimationWidget::QmitkSliceAnimationWidget(QWidget* parent) : QmitkAnimationWidget(parent), m_Ui(new Ui::QmitkSliceAnimationWidget) { m_Ui->setupUi(this); this->connect(m_Ui->windowComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnRenderWindowChanged(int))); this->connect(m_Ui->sliceRangeWidget, SIGNAL(minimumValueChanged(double)), this, SLOT(OnFromChanged(double))); this->connect(m_Ui->sliceRangeWidget, SIGNAL(maximumValueChanged(double)), this, SLOT(OnToChanged(double))); this->connect(m_Ui->reverseCheckBox, SIGNAL(clicked(bool)), this, SLOT(OnReverseChanged(bool))); } QmitkSliceAnimationWidget::~QmitkSliceAnimationWidget() { } void QmitkSliceAnimationWidget::SetAnimationItem(QmitkAnimationItem* sliceAnimationItem) { m_AnimationItem = dynamic_cast(sliceAnimationItem); if (m_AnimationItem == NULL) return; m_Ui->windowComboBox->setCurrentIndex(m_AnimationItem->GetRenderWindow()); + m_Ui->sliceRangeWidget->setMaximum(GetNumberOfSlices(m_AnimationItem->GetRenderWindow()) - 1); m_Ui->sliceRangeWidget->setValues(m_AnimationItem->GetFrom(), m_AnimationItem->GetTo()); m_Ui->reverseCheckBox->setChecked(m_AnimationItem->GetReverse()); } void QmitkSliceAnimationWidget::OnRenderWindowChanged(int renderWindow) { if (m_AnimationItem == NULL) return; + const int lastSlice = static_cast(GetNumberOfSlices(renderWindow) - 1); + + m_AnimationItem->SetFrom(0); + m_AnimationItem->SetTo(lastSlice); + + m_Ui->sliceRangeWidget->setMaximum(lastSlice); + m_Ui->sliceRangeWidget->setValues(m_AnimationItem->GetFrom(), m_AnimationItem->GetTo()); + if (m_AnimationItem->GetRenderWindow() != renderWindow) m_AnimationItem->SetRenderWindow(renderWindow); } void QmitkSliceAnimationWidget::OnFromChanged(double from) { if (m_AnimationItem == NULL) return; int intFrom = static_cast(from); if (m_AnimationItem->GetFrom() != intFrom) m_AnimationItem->SetFrom(intFrom); } void QmitkSliceAnimationWidget::OnToChanged(double to) { if (m_AnimationItem == NULL) return; int intTo = static_cast(to); if (m_AnimationItem->GetTo() != intTo) m_AnimationItem->SetTo(intTo); } void QmitkSliceAnimationWidget::OnReverseChanged(bool reverse) { if (m_AnimationItem == NULL) return; if (m_AnimationItem->GetReverse() != reverse) m_AnimationItem->SetReverse(reverse); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.ui b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.ui index 303795a91b..86f72fc9d9 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.ui +++ b/Plugins/org.mitk.gui.qt.moviemaker2/src/internal/QmitkSliceAnimationWidget.ui @@ -1,117 +1,112 @@ QmitkSliceAnimationWidget 0 0 304 96 QmitkSliceAnimationWidget 0 0 Window: windowComboBox 0 0 Axial Sagittal Coronal - - - 3D - - 0 0 Slice range: 0 999.000000000000000 999.000000000000000 0 0 Reverse ctkRangeWidget QWidget
ctkRangeWidget.h
windowComboBox reverseCheckBox