From 3f439c1ef2c1f4b7694d05be53117f0afe97cc17 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sun, 5 Jan 2020 13:51:12 +0100 Subject: [PATCH 1/2] Enable screenshot and recording feature --- README.md | 9 +++++++ gui/base.qrc | 2 ++ gui/basewindow.cc | 50 +++++++++++++++++++++++++++++++++++++++ gui/basewindow.h | 6 +++++ gui/fpgaviewwidget.cc | 38 ++++++++++++++++++++++++++++- gui/fpgaviewwidget.h | 8 ++++++- gui/resources/camera.png | Bin 0 -> 665 bytes gui/resources/film.png | Bin 0 -> 653 bytes 8 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 gui/resources/camera.png create mode 100644 gui/resources/film.png diff --git a/README.md b/README.md index cf743732..378d50b7 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,15 @@ Notes for developers - To automatically format all source code, run `make clangformat`. - See the wiki for additional documentation on the architecture API. +Recording a movie +----------------- + +- To save a movie recording of place-and-route click recording icon in toolbar and select empty directory + where recording files will be stored and select frames to skip. +- Manualy start all PnR operations you wish +- Click on recording icon again to stop recording +- Go to directory containing files and exeecute `ffmpeg -f image2 -r 1 -i movie_%05d.png -c:v libx264 nextpnr.mp4` + Testing ------- diff --git a/gui/base.qrc b/gui/base.qrc index 63612bf4..0671fa9e 100644 --- a/gui/base.qrc +++ b/gui/base.qrc @@ -28,5 +28,7 @@ resources/wire.png resources/pip.png resources/group.png + resources/camera.png + resources/film.png diff --git a/gui/basewindow.cc b/gui/basewindow.cc index c3dbc131..0bfc59a3 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -252,6 +253,18 @@ void BaseMainWindow::createMenusAndBars() actionDisplayGroups->setChecked(true); connect(actionDisplayGroups, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals); + actionScreenshot = new QAction("Screenshot", this); + actionScreenshot->setIcon(QIcon(":/icons/resources/camera.png")); + actionScreenshot->setStatusTip("Taking a screenshot"); + connect(actionScreenshot, &QAction::triggered, this, &BaseMainWindow::screenshot); + + actionMovie = new QAction("Recording", this); + actionMovie->setIcon(QIcon(":/icons/resources/film.png")); + actionMovie->setStatusTip("Saving a movie"); + actionMovie->setCheckable(true); + actionMovie->setChecked(false); + connect(actionMovie, &QAction::triggered, this, &BaseMainWindow::saveMovie); + // set initial state fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(), actionDisplayPip->isChecked(), actionDisplayGroups->isChecked()); @@ -317,6 +330,9 @@ void BaseMainWindow::createMenusAndBars() deviceViewToolBar->addAction(actionDisplayWire); deviceViewToolBar->addAction(actionDisplayPip); deviceViewToolBar->addAction(actionDisplayGroups); + deviceViewToolBar->addSeparator(); + deviceViewToolBar->addAction(actionScreenshot); + deviceViewToolBar->addAction(actionMovie); // Add status bar with progress bar statusBar = new QStatusBar(); @@ -362,6 +378,40 @@ void BaseMainWindow::save_json() } } +void BaseMainWindow::screenshot() +{ + QString fileName = QFileDialog::getSaveFileName(this, QString("Save screenshot"), QString(), QString("*.png")); + if (!fileName.isEmpty()) { + QImage image = fpgaView->grabFramebuffer(); + if (!fileName.endsWith(".png")) + fileName += ".png"; + QImageWriter imageWriter(fileName, "png"); + if (imageWriter.write(image)) + log("Saving screenshot successful.\n"); + else + log("Saving screenshot failed.\n"); + } +} + +void BaseMainWindow::saveMovie() +{ + if (actionMovie->isChecked()) { + QString dir = QFileDialog::getExistingDirectory(this, tr("Select Movie Directory"), QDir::currentPath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (!dir.isEmpty()) { + bool ok; + int frames = QInputDialog::getInt(this, "Skip frames", tr("Frames to skip (1 frame = 50ms):"), 5, 0, 1000, + 1, &ok); + if (ok) + fpgaView->movieStart(dir, frames); + else + actionMovie->setChecked(false); + } else + actionMovie->setChecked(false); + } else { + fpgaView->movieStop(); + } +} void BaseMainWindow::pack_finished(bool status) { disableActions(); diff --git a/gui/basewindow.h b/gui/basewindow.h index 7562307e..fe9dfdf2 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -83,6 +83,9 @@ class BaseMainWindow : public QMainWindow void taskStarted(); void taskPaused(); + void screenshot(); + void saveMovie(); + Q_SIGNALS: void contextChanged(Context *ctx); void updateTreeView(); @@ -128,6 +131,9 @@ class BaseMainWindow : public QMainWindow QAction *actionDisplayWire; QAction *actionDisplayPip; QAction *actionDisplayGroups; + + QAction *actionScreenshot; + QAction *actionMovie; }; NEXTPNR_NAMESPACE_END diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 2e1bbf63..8730c9d9 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -22,6 +22,9 @@ #include #include +#include +#include +#include #include #include @@ -35,7 +38,7 @@ NEXTPNR_NAMESPACE_BEGIN FPGAViewWidget::FPGAViewWidget(QWidget *parent) - : QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f), + : QOpenGLWidget(parent), movieSaving(false), ctx_(nullptr), paintTimer_(this), lineShader_(this), zoom_(10.0f), rendererArgs_(new FPGAViewWidget::RendererArgs), rendererData_(new FPGAViewWidget::RendererData) { colors_.background = QColor("#000000"); @@ -322,6 +325,23 @@ void FPGAViewWidget::paintGL() lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected, thick11Px, matrix); lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered, thick2Px, matrix); + if (movieSaving) { + if (movieCounter == currentFrameSkip) { + QMutexLocker lock(&rendererArgsLock_); + movieCounter = 0; + currentMovieFrame++; + + QImage image = grabFramebuffer(); + QString number = QString("movie_%1.png").arg(currentMovieFrame, 5, 10, QChar('0')); + + QFileInfo fileName = QFileInfo(QDir(movieDir), number); + QImageWriter imageWriter(fileName.absoluteFilePath(), "png"); + imageWriter.write(image); + } else { + movieCounter++; + } + } + // Render ImGui QtImGui::newFrame(); QMutexLocker lock(&rendererArgsLock_); @@ -579,6 +599,22 @@ void FPGAViewWidget::renderLines(void) } } +void FPGAViewWidget::movieStart(QString dir, long frameSkip) +{ + QMutexLocker locker(&rendererArgsLock_); + movieDir = dir; + currentMovieFrame = 0; + movieCounter = 0; + currentFrameSkip = frameSkip; + movieSaving = true; +} + +void FPGAViewWidget::movieStop() +{ + QMutexLocker locker(&rendererArgsLock_); + movieSaving = false; +} + void FPGAViewWidget::onSelectedArchItem(std::vector decals, bool keep) { { diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 735590ba..7f99408e 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -120,13 +120,19 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void zoomSelected(); void zoomOutbound(); void enableDisableDecals(bool bels, bool wires, bool pips, bool groups); - + void movieStart(QString dir, long frameSkip); + void movieStop(); Q_SIGNALS: void clickedBel(BelId bel, bool add); void clickedWire(WireId wire, bool add); void clickedPip(PipId pip, bool add); private: + QString movieDir; + long currentMovieFrame; + long currentFrameSkip; + long movieCounter; + bool movieSaving; const float zoomNear_ = 0.05f; // do not zoom closer than this float zoomFar_ = 10.0f; // do not zoom further than this const float zoomLvl1_ = 1.0f; diff --git a/gui/resources/camera.png b/gui/resources/camera.png new file mode 100644 index 0000000000000000000000000000000000000000..8536d1a795888d8d396ac4211b639c6395dd08e6 GIT binary patch literal 665 zcmV;K0%rY*P)gJ-O!jxnD7oNLq;%;3B=$mW~C-A78b#}Ym zUa~ApY&IL$YPE8+w{2VKx^DRW{x^j};R(m*^Z8S9-^=ClFHtNO4^>s2WHK4B90Evd zRNvv@{p&Eky~2wRC-6pYqSb0Y8jr^>3G5|f?PqEtk*KFqDSspqL8sF}uh+wHIK<_n z6MX#q6V3T;+&aAjAq2YJ?gK?pbdSdaP1D3|HoL3QsY$Qbi+a6|a5#)~It`Knl7dhu zghr!*N~MB8Ab@N(i+;b4$z&p9n$6}7jROaR0fNCGwg*Re{(gmpDPU86x^Us%)obw2 z=aha6oFxO>?KYy(sFbSkv{)<Xg3cSx! zV*;wSwBMGSfGRX({TECm3Yn@1ESF0eb3TJT z!7c-;DBO+#(G_BEZ~a#M_t*Fbci5Cwkwgrt00000NkvXXu0mjf2e~nc literal 0 HcmV?d00001 diff --git a/gui/resources/film.png b/gui/resources/film.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ce7bb198a3b268bd634d2b26e9b710f3797d37 GIT binary patch literal 653 zcmV;80&@L{P)WO3(`_cf+b25@DJ#zdQm}8GzWtq2-QnZ8W6mB^kfeK5f%S{ zUW%tGMCwrwic~ZrQcG=4f?5bkV+3dRk8hw6bk~y$KX#b!y*J4EJ~>;dRASqrSu;ZpM>?P}K~6AT zWv6Dmq?v&9LdXC(m%WCO6ma_di$R(v$@ad_>@R41N3N5lSJq9@6CGhX84-$%Xrd_6 z;){?{E|Ytt5$S-&Au>t4wDlIxdkfe-a22LMj``McG};r8@{GsRPm*+8fFey6C)@ifDBXVyTw(N@Xd41b45OFg6x_QA zpwLiigyy~cVoPxW^r~C7ZQpr%>1$*HKmv~AY-qJw4;gUecS--wnqslISSS=^KA&Ic n@BK|Onfz#3R%n{$a)0j^sqv5F(1NTL00000NkvXXu0mjf3S}fX literal 0 HcmV?d00001 From 0bf8fa23d91ae9bc46a45a4a2d8d12061e54c742 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sat, 11 Jan 2020 15:48:43 +0100 Subject: [PATCH 2/2] Skip same frames --- gui/basewindow.cc | 14 +++++++++----- gui/fpgaviewwidget.cc | 19 ++++++++++++------- gui/fpgaviewwidget.h | 4 +++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 0bfc59a3..7f767c58 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include "designwidget.h" @@ -400,11 +401,14 @@ void BaseMainWindow::saveMovie() QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!dir.isEmpty()) { bool ok; - int frames = QInputDialog::getInt(this, "Skip frames", tr("Frames to skip (1 frame = 50ms):"), 5, 0, 1000, - 1, &ok); - if (ok) - fpgaView->movieStart(dir, frames); - else + int frames = + QInputDialog::getInt(this, "Recording", tr("Frames to skip (1 frame = 50ms):"), 5, 0, 1000, 1, &ok); + if (ok) { + QMessageBox::StandardButton reply = + QMessageBox::question(this, "Recording", "Skip identical frames ?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + fpgaView->movieStart(dir, frames, (reply == QMessageBox::Yes)); + } else actionMovie->setChecked(false); } else actionMovie->setChecked(false); diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 8730c9d9..f1494452 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -329,14 +329,17 @@ void FPGAViewWidget::paintGL() if (movieCounter == currentFrameSkip) { QMutexLocker lock(&rendererArgsLock_); movieCounter = 0; - currentMovieFrame++; - QImage image = grabFramebuffer(); - QString number = QString("movie_%1.png").arg(currentMovieFrame, 5, 10, QChar('0')); + if (!movieSkipSame || movieLastImage != image) { + currentMovieFrame++; - QFileInfo fileName = QFileInfo(QDir(movieDir), number); - QImageWriter imageWriter(fileName.absoluteFilePath(), "png"); - imageWriter.write(image); + QString number = QString("movie_%1.png").arg(currentMovieFrame, 5, 10, QChar('0')); + + QFileInfo fileName = QFileInfo(QDir(movieDir), number); + QImageWriter imageWriter(fileName.absoluteFilePath(), "png"); + imageWriter.write(image); + movieLastImage = image; + } } else { movieCounter++; } @@ -599,9 +602,11 @@ void FPGAViewWidget::renderLines(void) } } -void FPGAViewWidget::movieStart(QString dir, long frameSkip) +void FPGAViewWidget::movieStart(QString dir, long frameSkip, bool skipSame) { QMutexLocker locker(&rendererArgsLock_); + movieLastImage = QImage(); + movieSkipSame = skipSame; movieDir = dir; currentMovieFrame = 0; movieCounter = 0; diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index 7f99408e..9f670cb0 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -120,7 +120,7 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void zoomSelected(); void zoomOutbound(); void enableDisableDecals(bool bels, bool wires, bool pips, bool groups); - void movieStart(QString dir, long frameSkip); + void movieStart(QString dir, long frameSkip, bool skipSame); void movieStop(); Q_SIGNALS: void clickedBel(BelId bel, bool add); @@ -133,6 +133,8 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions long currentFrameSkip; long movieCounter; bool movieSaving; + bool movieSkipSame; + QImage movieLastImage; const float zoomNear_ = 0.05f; // do not zoom closer than this float zoomFar_ = 10.0f; // do not zoom further than this const float zoomLvl1_ = 1.0f;