aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiodrag Milanović <mmicko@gmail.com>2020-01-11 16:29:26 +0100
committerGitHub <noreply@github.com>2020-01-11 16:29:26 +0100
commit56918e5596a8531c5a21bed8e9407fa0ea7aa507 (patch)
treee3ba4ae0f3abae7d5c09debf76bfb872feb64d56
parentabfe31d5d22a0ed1cc6ef32cf73fc1826b090b1c (diff)
parent0bf8fa23d91ae9bc46a45a4a2d8d12061e54c742 (diff)
downloadnextpnr-56918e5596a8531c5a21bed8e9407fa0ea7aa507.tar.gz
nextpnr-56918e5596a8531c5a21bed8e9407fa0ea7aa507.tar.bz2
nextpnr-56918e5596a8531c5a21bed8e9407fa0ea7aa507.zip
Merge pull request #377 from YosysHQ/mmicko/recording
Enable screenshot and recording feature
-rw-r--r--README.md9
-rw-r--r--gui/base.qrc2
-rw-r--r--gui/basewindow.cc54
-rw-r--r--gui/basewindow.h6
-rw-r--r--gui/fpgaviewwidget.cc43
-rw-r--r--gui/fpgaviewwidget.h10
-rw-r--r--gui/resources/camera.pngbin0 -> 665 bytes
-rw-r--r--gui/resources/film.pngbin0 -> 653 bytes
8 files changed, 122 insertions, 2 deletions
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 @@
<file>resources/wire.png</file>
<file>resources/pip.png</file>
<file>resources/group.png</file>
+ <file>resources/camera.png</file>
+ <file>resources/film.png</file>
</qresource>
</RCC>
diff --git a/gui/basewindow.cc b/gui/basewindow.cc
index c3dbc131..7f767c58 100644
--- a/gui/basewindow.cc
+++ b/gui/basewindow.cc
@@ -23,7 +23,9 @@
#include <QFileDialog>
#include <QGridLayout>
#include <QIcon>
+#include <QImageWriter>
#include <QInputDialog>
+#include <QMessageBox>
#include <QSplitter>
#include <fstream>
#include "designwidget.h"
@@ -252,6 +254,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 +331,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 +379,43 @@ 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, "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);
+ } 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..f1494452 100644
--- a/gui/fpgaviewwidget.cc
+++ b/gui/fpgaviewwidget.cc
@@ -22,6 +22,9 @@
#include <QApplication>
#include <QCoreApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QImageWriter>
#include <QMouseEvent>
#include <QWidget>
@@ -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,26 @@ 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;
+ QImage image = grabFramebuffer();
+ if (!movieSkipSame || movieLastImage != image) {
+ currentMovieFrame++;
+
+ 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++;
+ }
+ }
+
// Render ImGui
QtImGui::newFrame();
QMutexLocker lock(&rendererArgsLock_);
@@ -579,6 +602,24 @@ void FPGAViewWidget::renderLines(void)
}
}
+void FPGAViewWidget::movieStart(QString dir, long frameSkip, bool skipSame)
+{
+ QMutexLocker locker(&rendererArgsLock_);
+ movieLastImage = QImage();
+ movieSkipSame = skipSame;
+ movieDir = dir;
+ currentMovieFrame = 0;
+ movieCounter = 0;
+ currentFrameSkip = frameSkip;
+ movieSaving = true;
+}
+
+void FPGAViewWidget::movieStop()
+{
+ QMutexLocker locker(&rendererArgsLock_);
+ movieSaving = false;
+}
+
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals, bool keep)
{
{
diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h
index 735590ba..9f670cb0 100644
--- a/gui/fpgaviewwidget.h
+++ b/gui/fpgaviewwidget.h
@@ -120,13 +120,21 @@ 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, bool skipSame);
+ 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;
+ 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;
diff --git a/gui/resources/camera.png b/gui/resources/camera.png
new file mode 100644
index 00000000..8536d1a7
--- /dev/null
+++ b/gui/resources/camera.png
Binary files differ
diff --git a/gui/resources/film.png b/gui/resources/film.png
new file mode 100644
index 00000000..b0ce7bb1
--- /dev/null
+++ b/gui/resources/film.png
Binary files differ