diff --git a/app/core.cpp b/app/core.cpp index d6989319a..0aa5fa11a 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -394,10 +394,7 @@ void Core::DialogExportShow() rational time; if (GetSequenceToExport(&viewer, &time)) { - ExportDialog* ed = new ExportDialog(viewer, main_window_); - ed->SetTime(time); - connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); - ed->open(); + OpenExportDialogForViewer(viewer, time, false); } } @@ -1253,6 +1250,14 @@ void Core::OpenNodeInViewer(ViewerOutput *viewer) main_window_->OpenNodeInViewer(viewer); } +void Core::OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image) +{ + ExportDialog* ed = new ExportDialog(viewer, start_still_image, main_window_); + ed->SetTime(time); + connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); + ed->open(); +} + void Core::CheckForAutoRecoveries() { QFile autorecovery_index(GetAutoRecoveryIndexFilename()); diff --git a/app/core.h b/app/core.h index 1c7a923ef..c1902f17a 100644 --- a/app/core.h +++ b/app/core.h @@ -313,6 +313,8 @@ public: void OpenNodeInViewer(ViewerOutput* viewer); + void OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image); + public slots: /** * @brief Starts an open file dialog to load a project from file diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 54a1403a8..0ffcbb4c3 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -44,9 +44,10 @@ namespace olive { #define super QDialog -ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : +ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWidget *parent) : super(parent), - viewer_node_(viewer_node) + viewer_node_(viewer_node), + stills_only_mode_(stills_only_mode) { QHBoxLayout* layout = new QHBoxLayout(this); @@ -238,7 +239,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : subtitles_enabled_->setEnabled(has_subtitle_tracks); // If the viewer already has cached params, use them - if (viewer_node_->GetLastUsedEncodingParams().IsValid()) { + if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) { SetParams(viewer_node_->GetLastUsedEncodingParams()); } else { SetDefaults(); @@ -572,7 +573,11 @@ bool ExportDialog::SequenceHasSubtitles() const void ExportDialog::SetDefaults() { - format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); + if (!stills_only_mode_) { + format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); + } else { + format_combobox_->SetFormat(ExportFormat::kFormatPNG); + } FormatChanged(format_combobox_->GetFormat()); VideoParams vp = viewer_node_->GetVideoParams(); @@ -745,7 +750,9 @@ void ExportDialog::done(int r) { preview_viewer_->ConnectViewerNode(nullptr); - viewer_node_->SetLastUsedEncodingParams(GenerateParams()); + if (!stills_only_mode_) { + viewer_node_->SetLastUsedEncodingParams(GenerateParams()); + } super::done(r); } diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index b9cd62b73..2b257cec2 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -43,7 +43,10 @@ class ExportDialog : public QDialog { Q_OBJECT public: - ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr); + ExportDialog(ViewerOutput* viewer_node, bool stills_only_mode, QWidget* parent = nullptr); + ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr) : + ExportDialog(viewer_node, false, parent) + {} rational GetSelectedTimebase() const; void SetSelectedTimebase(const rational &r); @@ -116,6 +119,8 @@ private: QWidget* preferences_area_; QCheckBox *export_bkg_box_; + bool stills_only_mode_; + private slots: void BrowseFilename(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d786d1b48..134874425 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -588,6 +588,11 @@ void ViewerWidget::RequestNextDryRun() } } +void ViewerWidget::SaveFrameAsImage() +{ + Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), GetTime(), true); +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -1385,6 +1390,11 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) }); } + menu.addSeparator(); + + auto save_frame_as_image = menu.addAction(tr("Save Frame As Image")); + connect(save_frame_as_image, &QAction::triggered, this, &ViewerWidget::SaveFrameAsImage); + menu.exec(static_cast(sender())->mapToGlobal(pos)); } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index e4aaa6f0e..4c3a18551 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -368,6 +368,8 @@ private slots: void RequestNextDryRun(); + void SaveFrameAsImage(); + }; }