diff --git a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.cpp b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.cpp index 103fa15b36..dca9656896 100644 --- a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.cpp +++ b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.cpp @@ -1,418 +1,276 @@ /*============================================================================ 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 "QmitkSimpleExampleView.h" +#include #include #include #include #include -#include -#include #include "QmitkRenderWindow.h" #include "QmitkStepperAdapter.h" -#include "QmitkFFmpegWriter.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateProperty.h" #include "mitkProperties.h" -#include -#include -#include #include #include #include -#include const std::string QmitkSimpleExampleView::VIEW_ID = "org.mitk.views.simpleexample"; QmitkSimpleExampleView::QmitkSimpleExampleView() : m_Controls(nullptr), m_NavigatorsInitialized(false), m_Parent(nullptr) { } QmitkSimpleExampleView::~QmitkSimpleExampleView() { } void QmitkSimpleExampleView::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { m_Parent = parent; // create GUI widgets m_Controls = new Ui::QmitkSimpleExampleViewControls; m_Controls->setupUi(parent); this->CreateConnections(); this->RenderWindowPartActivated(this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)); } } void QmitkSimpleExampleView::SetFocus() { m_Controls->renderWindowComboBox->setFocus(); } void QmitkSimpleExampleView::RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) { if (renderWindowPart == nullptr) { m_Parent->setEnabled(false); return; } QHashIterator renderIter(renderWindowPart->GetQmitkRenderWindows()); while (renderIter.hasNext()) { renderIter.next(); m_Controls->renderWindowComboBox->addItem(renderIter.key()); } RenderWindowSelected(m_Controls->renderWindowComboBox->currentText()); m_TimeStepper.reset(new QmitkStepperAdapter(m_Controls->timeSliceNavigationWidget, renderWindowPart->GetTimeNavigationController()->GetTime())); m_MovieStepper.reset(new QmitkStepperAdapter(m_Controls->movieNavigatorTime, renderWindowPart->GetTimeNavigationController()->GetTime())); m_Parent->setEnabled(true); } void QmitkSimpleExampleView::RenderWindowPartDeactivated(mitk::IRenderWindowPart * /*renderWindowPart*/) { m_Parent->setEnabled(false); m_SliceStepper.reset(); m_TimeStepper.reset(); m_MovieStepper.reset(); m_Controls->renderWindowComboBox->clear(); } void QmitkSimpleExampleView::CreateConnections() { if (m_Controls) { connect(m_Controls->renderWindowComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(RenderWindowSelected(QString))); connect(m_Controls->stereoSelect, SIGNAL(activated(int)), this, SLOT(StereoSelectionChanged(int))); connect(m_Controls->reInitializeNavigatorsButton, SIGNAL(clicked()), this, SLOT(InitNavigators())); - connect(m_Controls->genMovieButton, SIGNAL(clicked()), this, SLOT(GenerateMovie())); connect(m_Controls->m_TakeScreenshotBtn, SIGNAL(clicked()), this, SLOT(OnTakeScreenshot())); connect(m_Controls->m_TakeHighResScreenShotBtn, SIGNAL(clicked()), this, SLOT(OnTakeHighResolutionScreenshot())); } } void QmitkSimpleExampleView::InitNavigators() { /* get all nodes that have not set "includeInBoundingBox" to false */ mitk::NodePredicateNot::Pointer pred = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false))); mitk::DataStorage::SetOfObjects::ConstPointer rs = this->GetDataStorage()->GetSubset(pred); /* calculate bounding geometry of these nodes */ auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(rs); /* initialize the views to the bounding geometry */ m_NavigatorsInitialized = mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } -/** - * Returns path to the ffmpeg lib if configured in preferences. - * - * This implementation has been reused from MovieMaker view. - * - * @return The path to ffmpeg lib or empty string if not configured. - */ -QString QmitkSimpleExampleView::GetFFmpegPath() const -{ - auto* preferences = - mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.gui.qt.ext.externalprograms"); - - return preferences != nullptr - ? QString::fromStdString(preferences->Get("ffmpeg", "")) - : QString(""); -} - -/** - * Reads pixels from specified render window. - * - * This implementation has been reused from MovieMaker view. - * - * @param renderWindow - * @param x - * @param y - * @param width - * @param height - * @return - */ -static unsigned char *ReadPixels(vtkRenderWindow *renderWindow, int x, int y, int width, int height) -{ - if (renderWindow == nullptr) - return nullptr; - - unsigned char *frame = new unsigned char[width * height * 3]; - - renderWindow->MakeCurrent(); - glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame); - - return frame; -} - -/** - * Records a movie from the selected render window with a default frame rate of 30 Hz. - * - * Parts of this implementation have been reused from MovieMaker view. - */ -void QmitkSimpleExampleView::GenerateMovie() -{ - QmitkRenderWindow *movieRenderWindow = GetSelectedRenderWindow(); - - mitk::Stepper::Pointer stepper = movieRenderWindow->GetSliceNavigationController()->GetSlice(); - - QmitkFFmpegWriter *movieWriter = new QmitkFFmpegWriter(m_Parent); - - const QString ffmpegPath = GetFFmpegPath(); - - if (ffmpegPath.isEmpty()) - { - QMessageBox::information( - nullptr, - "Movie Maker", - "

Set path to FFmpeg1 in preferences (Window -> Preferences... " - "(Ctrl+P) -> External Programs) to be able to record your movies to video files.

" - "

If you are using Linux, chances are good that FFmpeg is included in the official package " - "repositories.

" - "

[1] Download FFmpeg from ffmpeg.org

"); - return; - } - - movieWriter->SetFFmpegPath(GetFFmpegPath()); - - vtkRenderWindow *renderWindow = movieRenderWindow->renderWindow(); - - if (renderWindow == nullptr) - 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; - - movieWriter->SetSize(width, height); - movieWriter->SetFramerate(30); - - QString saveFileName = QFileDialog::getSaveFileName(nullptr, "Specify a filename", "", "Movie (*.mp4)"); - - if (saveFileName.isEmpty()) - return; - - if (!saveFileName.endsWith(".mp4")) - saveFileName += ".mp4"; - - movieWriter->SetOutputPath(saveFileName); - - const unsigned int numberOfFrames = stepper->GetSteps() - stepper->GetPos(); - - try - { - movieWriter->Start(); - - for (unsigned int currentFrame = 0; currentFrame < numberOfFrames; ++currentFrame) - { - mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); - - renderWindow->MakeCurrent(); - unsigned char *frame = ReadPixels(renderWindow, x, y, width, height); - movieWriter->WriteFrame(frame); - delete[] frame; - - stepper->Next(); - } - - movieWriter->Stop(); - - mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); - } - catch (const mitk::Exception &exception) - { - mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); - - QMessageBox::critical(nullptr, "Generate Movie", exception.GetDescription()); - } -} - void QmitkSimpleExampleView::StereoSelectionChanged(int id) { /* From vtkRenderWindow.h tells us about stereo rendering: Set/Get what type of stereo rendering to use. CrystalEyes mode uses frame-sequential capabilities available in OpenGL to drive LCD shutter glasses and stereo projectors. RedBlue mode is a simple type of stereo for use with red-blue glasses. Anaglyph mode is a superset of RedBlue mode, but the color output channels can be configured using the AnaglyphColorMask and the color of the original image can be (somewhat maintained using AnaglyphColorSaturation; the default colors for Anaglyph mode is red-cyan. Interlaced stereo mode produces a composite image where horizontal lines alternate between left and right views. StereoLeft and StereoRight modes choose one or the other stereo view. Dresden mode is yet another stereoscopic interleaving. */ auto *renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); vtkRenderWindow *vtkrenderwindow = renderWindowPart->GetQmitkRenderWindow("3d")->GetVtkRenderWindow(); // note: foreground vtkRenderers (at least the department logo renderer) produce errors in stereoscopic visualization. // Therefore, we disable the logo visualization during stereo rendering. switch (id) { case 0: vtkrenderwindow->StereoRenderOff(); break; case 1: vtkrenderwindow->SetStereoTypeToRedBlue(); vtkrenderwindow->StereoRenderOn(); renderWindowPart->EnableDecorations(false, QStringList(mitk::IRenderWindowPart::DECORATION_LOGO)); break; case 2: vtkrenderwindow->SetStereoTypeToDresden(); vtkrenderwindow->StereoRenderOn(); renderWindowPart->EnableDecorations(false, QStringList(mitk::IRenderWindowPart::DECORATION_LOGO)); break; } mitk::BaseRenderer::GetInstance(vtkrenderwindow)->SetMapperID(mitk::BaseRenderer::Standard3D); renderWindowPart->RequestUpdate(); } QmitkRenderWindow *QmitkSimpleExampleView::GetSelectedRenderWindow() const { QString id = m_Controls->renderWindowComboBox->currentText(); if (id.isEmpty()) { return nullptr; } else { return this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetQmitkRenderWindow(id); } } void QmitkSimpleExampleView::OnTakeHighResolutionScreenshot() { QString filter; QString fileName = QFileDialog::getSaveFileName( nullptr, "Save screenshot to...", QDir::currentPath(), m_PNGExtension + ";;" + m_JPGExtension, &filter); vtkRenderer *renderer = this->GetSelectedRenderWindow()->GetRenderer()->GetVtkRenderer(); if (renderer == nullptr) return; this->TakeScreenshot(renderer, 4, fileName, filter); } void QmitkSimpleExampleView::OnTakeScreenshot() { QString filter; QString fileName = QFileDialog::getSaveFileName( nullptr, "Save screenshot to...", QDir::currentPath(), m_PNGExtension + ";;" + m_JPGExtension, &filter); QmitkRenderWindow *renWin = this->GetSelectedRenderWindow(); if (renWin == nullptr) return; vtkRenderer *renderer = renWin->GetRenderer()->GetVtkRenderer(); if (renderer == nullptr) return; this->TakeScreenshot(renderer, 1, fileName, filter); } void QmitkSimpleExampleView::TakeScreenshot(vtkRenderer *renderer, unsigned int magnificationFactor, QString fileName, QString filter) { if ((renderer == nullptr) || (magnificationFactor < 1) || fileName.isEmpty()) return; bool doubleBuffering(renderer->GetRenderWindow()->GetDoubleBuffer()); renderer->GetRenderWindow()->DoubleBufferOff(); vtkImageWriter *fileWriter = nullptr; QFileInfo fi(fileName); QString suffix = fi.suffix().toLower(); if (suffix.isEmpty() || (suffix != "png" && suffix != "jpg" && suffix != "jpeg")) { if (filter == m_PNGExtension) { suffix = "png"; } else if (filter == m_JPGExtension) { suffix = "jpg"; } fileName += "." + suffix; } if (suffix.compare("jpg", Qt::CaseInsensitive) == 0 || suffix.compare("jpeg", Qt::CaseInsensitive) == 0) { vtkJPEGWriter *w = vtkJPEGWriter::New(); w->SetQuality(100); w->ProgressiveOff(); fileWriter = w; } else // default is png { fileWriter = vtkPNGWriter::New(); } vtkRenderLargeImage *magnifier = vtkRenderLargeImage::New(); magnifier->SetInput(renderer); magnifier->SetMagnification(magnificationFactor); fileWriter->SetInputConnection(magnifier->GetOutputPort()); fileWriter->SetFileName(fileName.toLatin1()); // vtkRenderLargeImage has problems with different layers, therefore we have to // temporarily deactivate all other layers. // we set the background to white, because it is nicer than black... double oldBackground[3]; renderer->GetBackground(oldBackground); double white[] = {1.0, 1.0, 1.0}; renderer->SetBackground(white); mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); renderWindowPart->EnableDecorations(false); fileWriter->Write(); fileWriter->Delete(); renderWindowPart->EnableDecorations(true); renderer->SetBackground(oldBackground); renderer->GetRenderWindow()->SetDoubleBuffer(doubleBuffering); } void QmitkSimpleExampleView::RenderWindowSelected(const QString &id) { if (!id.isEmpty()) { m_SliceStepper.reset(new QmitkStepperAdapter(m_Controls->sliceNavigationWidget, this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN)->GetQmitkRenderWindow(id)->GetSliceNavigationController()->GetSlice())); } } diff --git a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.h b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.h index 064a2078ee..d6cc2df5db 100644 --- a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.h +++ b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleView.h @@ -1,105 +1,100 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QmitkSimpleExampleView_h #define QmitkSimpleExampleView_h #include #include -#include "ui_QmitkSimpleExampleViewControls.h" - class QmitkStepperAdapter; class vtkRenderer; +namespace Ui +{ + class QmitkSimpleExampleViewControls; +} + /** * \brief QmitkSimpleExampleView * * \sa QmitkAbstractView */ class QmitkSimpleExampleView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // this is needed for all Qt objects that should have a MOC object (everything that derives from QObject) Q_OBJECT public: static const std::string VIEW_ID; QmitkSimpleExampleView(); ~QmitkSimpleExampleView() override; private: void CreateQtPartControl(QWidget *parent) override; void SetFocus() override; /// \brief Creation of the connections of main and control widget virtual void CreateConnections(); void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) override; /** * return the renderwindow of which the movie shall be created, what depends on the combo box */ QmitkRenderWindow *GetSelectedRenderWindow() const; /// writes a screenshot in JPEG or PNG format to the file fileName void TakeScreenshot(vtkRenderer *renderer, unsigned int magnificationFactor, QString fileName, QString filter = ""); - /// returns path to the ffmpeg lib if configured in preferences - QString GetFFmpegPath() const; - private slots: /** * Qt slot for reacting on the selected render window from the combo box */ void RenderWindowSelected(const QString &id); /** * qt slot for event processing from a qt widget defining the stereo mode of a render window */ void StereoSelectionChanged(int id); /** * initialize the slice and temporal sliders according to the image dimensions */ void InitNavigators(); - /** - * generate a movie as *.avi from the active render window - */ - void GenerateMovie(); - /// takes screenshot of the 3D window in 4x resolution of the render window void OnTakeHighResolutionScreenshot(); /// takes screenshot of the selected render window void OnTakeScreenshot(); private: Ui::QmitkSimpleExampleViewControls *m_Controls; bool m_NavigatorsInitialized; QScopedPointer m_SliceStepper; QScopedPointer m_TimeStepper; QScopedPointer m_MovieStepper; QWidget *m_Parent; QString m_PNGExtension = "PNG File (*.png)"; QString m_JPGExtension = "JPEG File (*.jpg)"; }; #endif diff --git a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleViewControls.ui b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleViewControls.ui index 565fb87a5a..834c18db88 100644 --- a/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleViewControls.ui +++ b/Examples/Plugins/org.mitk.example.gui.imaging/src/internal/simpleexample/QmitkSimpleExampleViewControls.ui @@ -1,229 +1,216 @@ QmitkSimpleExampleViewControls 0 0 298 610 0 0 false Form1 Qt::Vertical QSizePolicy::Minimum 20 10 Render Window Slice 1 0 Time 1 0 Time-Step Animation 1 0 Re-initialize Navigators Qt::Vertical QSizePolicy::Minimum 20 20 200 0 stereo off red-blue stereo D4D stereo Take Screenshot Screenshot will be 4 times larger than current render window size Take HighDef 3D Screenshot - - - - - 32767 - 23 - - - - Generate Movie - - - Qt::Vertical 20 40 QmitkSliceNavigationWidget QWidget
QmitkSliceNavigationWidget.h
1
QmitkPrimitiveMovieNavigatorWidget QWidget
QmitkPrimitiveMovieNavigatorWidget.h
1
diff --git a/Modules/QtWidgetsExt/files.cmake b/Modules/QtWidgetsExt/files.cmake index 8bbfea3d04..8a7b348f0a 100644 --- a/Modules/QtWidgetsExt/files.cmake +++ b/Modules/QtWidgetsExt/files.cmake @@ -1,96 +1,94 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES qclickablelabel.cpp QmitkAboutDialog.cpp QmitkBasePropertyView.cpp QmitkBoolPropertyWidget.cpp QmitkBoundingObjectWidget.cpp QmitkCallbackFromGUIThread.cpp QmitkColorPropertyEditor.cpp QmitkColorPropertyView.cpp QmitkColorTransferFunctionCanvas.cpp QmitkCrossWidget.cpp QmitkEditPointDialog.cpp QmitkEnumerationPropertyWidget.cpp - QmitkFFmpegWriter.cpp QmitkFileChooser.cpp QmitkGnuplotWidget.cpp QmitkHistogram.cpp QmitkHotkeyLineEdit.cpp QmitkModulesDialog.cpp QmitkModuleTableModel.cpp QmitkNumberPropertyEditor.cpp QmitkNumberPropertySlider.cpp QmitkNumberPropertyView.cpp QmitkPiecewiseFunctionCanvas.cpp QmitkPlotDialog.cpp QmitkPlotWidget.cpp QmitkPointListModel.cpp QmitkPointListView.cpp QmitkPointListWidget.cpp QmitkPrimitiveMovieNavigatorWidget.cpp QmitkPropertyViewFactory.cpp QmitkSliceWidget.cpp QmitkStandardViews.cpp QmitkStringPropertyEditor.cpp QmitkStringPropertyOnDemandEdit.cpp QmitkStringPropertyView.cpp QmitkTransferFunctionCanvas.cpp QmitkTransferFunctionGeneratorWidget.cpp QmitkTransferFunctionWidget.cpp QmitkUGCombinedRepresentationPropertyWidget.cpp QmitkVideoBackground.cpp QtWidgetsExtRegisterClasses.cpp ) set(MOC_H_FILES include/qclickablelabel.h include/QmitkAboutDialog.h include/QmitkBasePropertyView.h include/QmitkBoolPropertyWidget.h include/QmitkBoundingObjectWidget.h include/QmitkCallbackFromGUIThread.h include/QmitkColorPropertyEditor.h include/QmitkColorPropertyView.h include/QmitkColorTransferFunctionCanvas.h include/QmitkCrossWidget.h include/QmitkEditPointDialog.h include/QmitkEnumerationPropertyWidget.h - include/QmitkFFmpegWriter.h include/QmitkFileChooser.h include/QmitkGnuplotWidget.h include/QmitkHotkeyLineEdit.h include/QmitkNumberPropertyEditor.h include/QmitkNumberPropertySlider.h include/QmitkNumberPropertyView.h include/QmitkPiecewiseFunctionCanvas.h include/QmitkPlotWidget.h include/QmitkPointListModel.h include/QmitkPointListView.h include/QmitkPointListWidget.h include/QmitkPrimitiveMovieNavigatorWidget.h include/QmitkSliceWidget.h include/QmitkStandardViews.h include/QmitkStringPropertyEditor.h include/QmitkStringPropertyOnDemandEdit.h include/QmitkStringPropertyView.h include/QmitkTransferFunctionCanvas.h include/QmitkTransferFunctionGeneratorWidget.h include/QmitkTransferFunctionWidget.h include/QmitkUGCombinedRepresentationPropertyWidget.h include/QmitkVideoBackground.h ) set(UI_FILES src/QmitkAboutDialogGUI.ui src/QmitkGnuplotWidget.ui src/QmitkPrimitiveMovieNavigatorWidget.ui src/QmitkSliceWidget.ui src/QmitkTransferFunctionGeneratorWidget.ui src/QmitkTransferFunctionWidget.ui ) set(QRC_FILES resource/QtWidgetsExt.qrc ) diff --git a/Modules/QtWidgetsExt/include/QmitkFFmpegWriter.h b/Modules/QtWidgetsExt/include/QmitkFFmpegWriter.h deleted file mode 100644 index 30a66b4b68..0000000000 --- a/Modules/QtWidgetsExt/include/QmitkFFmpegWriter.h +++ /dev/null @@ -1,60 +0,0 @@ -/*============================================================================ - -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. - -============================================================================*/ - -#ifndef QmitkFFmpegWriter_h -#define QmitkFFmpegWriter_h - -#include -#include - -#include "MitkQtWidgetsExtExports.h" - -class MITKQTWIDGETSEXT_EXPORT QmitkFFmpegWriter : public QObject -{ - Q_OBJECT - -public: - explicit QmitkFFmpegWriter(QObject *parent = nullptr); - ~QmitkFFmpegWriter() override; - - 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/Modules/QtWidgetsExt/src/QmitkFFmpegWriter.cpp b/Modules/QtWidgetsExt/src/QmitkFFmpegWriter.cpp deleted file mode 100644 index dcb19fb36e..0000000000 --- a/Modules/QtWidgetsExt/src/QmitkFFmpegWriter.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/*============================================================================ - -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 "QmitkFFmpegWriter.h" -#include -#include -#include - -QmitkFFmpegWriter::QmitkFFmpegWriter(QObject *parent) - : QObject(parent), m_Process(new QProcess(this)), 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 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 == nullptr || !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 failed to start!"; - - case QProcess::Crashed: - mitkThrow() << "FFmpeg crashed!"; - - case QProcess::Timedout: - mitkThrow() << "FFmpeg timed out!"; - - case QProcess::WriteError: - mitkThrow() << "Could not write to FFmpeg!"; - - case QProcess::ReadError: - mitkThrow() << "Could not read from FFmpeg!"; - - 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 exit code: %1").arg(exitCode).toStdString().c_str(); - } - } - - m_Process->close(); -} 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 59a7a6f85e..8bb3f309f6 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp @@ -1,676 +1,715 @@ /*============================================================================ 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 #include #include +#include #include #include +#include + +#include +#include namespace { + class TemporaryDirectory + { + public: + TemporaryDirectory() + { + try + { + m_Path = mitk::IOUtil::CreateTemporaryDirectory("MITK_MovieMaker_XXXXXX"); + } + catch (...) + { + } + } + + ~TemporaryDirectory() + { + try + { + std::filesystem::remove_all(m_Path); + } + catch (...) + { + } + } + + bool IsValid() const + { + return !m_Path.empty() && std::filesystem::is_directory(m_Path); + } + + std::filesystem::path GetPath() const + { + return m_Path; + } + + TemporaryDirectory(const TemporaryDirectory&) = delete; + TemporaryDirectory& operator=(const TemporaryDirectory&) = delete; + + private: + std::filesystem::path m_Path; + }; + 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; } QString GetFFmpegPath() { auto* preferences = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.gui.qt.ext.externalprograms"); return preferences != nullptr ? QString::fromStdString(preferences->Get("ffmpeg", "")) : QString(); } - - void ReadPixels(std::unique_ptr& frame, vtkRenderWindow* renderWindow, int x, int y, int width, int height) - { - if (nullptr == renderWindow) - return; - - renderWindow->MakeCurrent(); - glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame.get()); - } } const std::string QmitkMovieMakerView::VIEW_ID = "org.mitk.views.moviemaker"; QmitkMovieMakerView::QmitkMovieMakerView() - : m_FFmpegWriter(nullptr), - m_Parent(nullptr), + : 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_FFmpegWriter = new QmitkFFmpegWriter(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(); } 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::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; const QString ffmpegPath = GetFFmpegPath(); if (ffmpegPath.isEmpty()) { QMessageBox::information(nullptr, "Movie Maker", "

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

"); return; } - m_FFmpegWriter->SetFFmpegPath(GetFFmpegPath()); - auto action = m_RecordMenu->exec(QCursor::pos()); if (nullptr == action) return; auto renderWindow = mitk::BaseRenderer::GetRenderWindowByName(action->data().toString().toStdString()); if (nullptr == renderWindow) 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; + QString saveFileName = QFileDialog::getSaveFileName(nullptr, "Specify a filename", "", "Movie (*.webm)"); - if (height & 1) - --height; - - if (width < 16 || height < 16) + if (saveFileName.isEmpty()) return; - m_FFmpegWriter->SetSize(width, height); - m_FFmpegWriter->SetFramerate(m_Ui->fpsSpinBox->value()); + if(!saveFileName.endsWith(".webm")) + saveFileName += ".webm"; - QString saveFileName = QFileDialog::getSaveFileName(nullptr, "Specify a filename", "", "Movie (*.mp4)"); + try + { + // Create a temporary directory and write all frames as PNGs into this directory. + // Call FFmpeg to create a video from these PNG images. + // Delete the temporary directory afterwards. - if (saveFileName.isEmpty()) - return; + TemporaryDirectory tempDir; - if(!saveFileName.endsWith(".mp4")) - saveFileName += ".mp4"; + if (!tempDir.IsValid()) + return; - m_FFmpegWriter->SetOutputPath(saveFileName); + auto windowToImage = vtkSmartPointer::New(); + windowToImage->SetInput(renderWindow); - try - { - auto frame = std::make_unique(width * height * 3); - m_FFmpegWriter->Start(); + auto imageWriter = vtkSmartPointer::New(); + imageWriter->SetInputConnection(windowToImage->GetOutputPort()); for (m_CurrentFrame = 0; m_CurrentFrame < m_NumFrames; ++m_CurrentFrame) { this->RenderCurrentFrame(); - ReadPixels(frame, renderWindow, x, y, width, height); - m_FFmpegWriter->WriteFrame(frame.get()); + windowToImage->Modified(); + + std::stringstream stream; + stream << std::setw(8) << std::setfill('0') << m_CurrentFrame << ".png"; + auto path = tempDir.GetPath() / stream.str(); + + imageWriter->SetFileName(path.string().c_str()); + imageWriter->Write(); } - m_FFmpegWriter->Stop(); + QProcess ffmpeg; - m_CurrentFrame = 0; - this->RenderCurrentFrame(); + ffmpeg.setWorkingDirectory(QString::fromStdString(tempDir.GetPath().string())); + ffmpeg.start(ffmpegPath, QStringList() + << "-y" // Override already existing files + << "-r" << QString::number(m_Ui->fpsSpinBox->value()) // Framerate + << "-i" << "%8d.png" // Input images + << "-c:v" << "libvpx-vp9" // VP9 codec + << "-crf" << QString::number(18) // Quality (constant rate factor) + << "-b:v" << QString::number(0) // Must be 0 for constant quality + << saveFileName); // Output video + + if (ffmpeg.waitForStarted()) + ffmpeg.waitForFinished(); } catch (const mitk::Exception& exception) { - m_CurrentFrame = 0; - this->RenderCurrentFrame(); - QMessageBox::critical(nullptr, "Movie Maker", exception.GetDescription()); } + + m_CurrentFrame = 0; + this->RenderCurrentFrame(); } 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; } diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h index e493845cfa..cbf3795b2f 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.h @@ -1,103 +1,101 @@ /*============================================================================ 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. ============================================================================*/ #ifndef QmitkMovieMakerView_h #define QmitkMovieMakerView_h #include #include #include #include class QmitkAnimationItem; class QmitkAnimationWidget; -class QmitkFFmpegWriter; class QMenu; class QStandardItemModel; class QTimer; namespace Ui { class QmitkMovieMakerView; } class QmitkMovieMakerView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkMovieMakerView(); ~QmitkMovieMakerView() override; void CreateQtPartControl(QWidget* parent) override; void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartInputChanged(mitk::IRenderWindowPart* renderWindowPart) override; 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(); void CalculateTotalDuration(); QmitkAnimationItem* GetSelectedAnimationItem() const; std::vector> GetActiveAnimations(double t) const; - QmitkFFmpegWriter* m_FFmpegWriter; QWidget* m_Parent; Ui::QmitkMovieMakerView* m_Ui; QStandardItemModel* m_AnimationModel; std::map m_AnimationWidgets; QMenu* m_AddAnimationMenu; QMenu* m_RecordMenu; QTimer* m_Timer; double m_TotalDuration; int m_NumFrames; int m_CurrentFrame; }; #endif