Enable screenshot and recording feature
This commit is contained in:
parent
abfe31d5d2
commit
3f439c1ef2
@ -161,6 +161,15 @@ Notes for developers
|
|||||||
- To automatically format all source code, run `make clangformat`.
|
- To automatically format all source code, run `make clangformat`.
|
||||||
- See the wiki for additional documentation on the architecture API.
|
- 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
|
Testing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -28,5 +28,7 @@
|
|||||||
<file>resources/wire.png</file>
|
<file>resources/wire.png</file>
|
||||||
<file>resources/pip.png</file>
|
<file>resources/pip.png</file>
|
||||||
<file>resources/group.png</file>
|
<file>resources/group.png</file>
|
||||||
|
<file>resources/camera.png</file>
|
||||||
|
<file>resources/film.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QImageWriter>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -252,6 +253,18 @@ void BaseMainWindow::createMenusAndBars()
|
|||||||
actionDisplayGroups->setChecked(true);
|
actionDisplayGroups->setChecked(true);
|
||||||
connect(actionDisplayGroups, &QAction::triggered, this, &BaseMainWindow::enableDisableDecals);
|
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
|
// set initial state
|
||||||
fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(),
|
fpgaView->enableDisableDecals(actionDisplayBel->isChecked(), actionDisplayWire->isChecked(),
|
||||||
actionDisplayPip->isChecked(), actionDisplayGroups->isChecked());
|
actionDisplayPip->isChecked(), actionDisplayGroups->isChecked());
|
||||||
@ -317,6 +330,9 @@ void BaseMainWindow::createMenusAndBars()
|
|||||||
deviceViewToolBar->addAction(actionDisplayWire);
|
deviceViewToolBar->addAction(actionDisplayWire);
|
||||||
deviceViewToolBar->addAction(actionDisplayPip);
|
deviceViewToolBar->addAction(actionDisplayPip);
|
||||||
deviceViewToolBar->addAction(actionDisplayGroups);
|
deviceViewToolBar->addAction(actionDisplayGroups);
|
||||||
|
deviceViewToolBar->addSeparator();
|
||||||
|
deviceViewToolBar->addAction(actionScreenshot);
|
||||||
|
deviceViewToolBar->addAction(actionMovie);
|
||||||
|
|
||||||
// Add status bar with progress bar
|
// Add status bar with progress bar
|
||||||
statusBar = new QStatusBar();
|
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)
|
void BaseMainWindow::pack_finished(bool status)
|
||||||
{
|
{
|
||||||
disableActions();
|
disableActions();
|
||||||
|
@ -83,6 +83,9 @@ class BaseMainWindow : public QMainWindow
|
|||||||
void taskStarted();
|
void taskStarted();
|
||||||
void taskPaused();
|
void taskPaused();
|
||||||
|
|
||||||
|
void screenshot();
|
||||||
|
void saveMovie();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void contextChanged(Context *ctx);
|
void contextChanged(Context *ctx);
|
||||||
void updateTreeView();
|
void updateTreeView();
|
||||||
@ -128,6 +131,9 @@ class BaseMainWindow : public QMainWindow
|
|||||||
QAction *actionDisplayWire;
|
QAction *actionDisplayWire;
|
||||||
QAction *actionDisplayPip;
|
QAction *actionDisplayPip;
|
||||||
QAction *actionDisplayGroups;
|
QAction *actionDisplayGroups;
|
||||||
|
|
||||||
|
QAction *actionScreenshot;
|
||||||
|
QAction *actionMovie;
|
||||||
};
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QImageWriter>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
@ -35,7 +38,7 @@
|
|||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
FPGAViewWidget::FPGAViewWidget(QWidget *parent)
|
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)
|
rendererArgs_(new FPGAViewWidget::RendererArgs), rendererData_(new FPGAViewWidget::RendererData)
|
||||||
{
|
{
|
||||||
colors_.background = QColor("#000000");
|
colors_.background = QColor("#000000");
|
||||||
@ -322,6 +325,23 @@ void FPGAViewWidget::paintGL()
|
|||||||
lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected, thick11Px, matrix);
|
lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected, thick11Px, matrix);
|
||||||
lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered, thick2Px, 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
|
// Render ImGui
|
||||||
QtImGui::newFrame();
|
QtImGui::newFrame();
|
||||||
QMutexLocker lock(&rendererArgsLock_);
|
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<DecalXY> decals, bool keep)
|
void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals, bool keep)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -120,13 +120,19 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
|||||||
void zoomSelected();
|
void zoomSelected();
|
||||||
void zoomOutbound();
|
void zoomOutbound();
|
||||||
void enableDisableDecals(bool bels, bool wires, bool pips, bool groups);
|
void enableDisableDecals(bool bels, bool wires, bool pips, bool groups);
|
||||||
|
void movieStart(QString dir, long frameSkip);
|
||||||
|
void movieStop();
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void clickedBel(BelId bel, bool add);
|
void clickedBel(BelId bel, bool add);
|
||||||
void clickedWire(WireId wire, bool add);
|
void clickedWire(WireId wire, bool add);
|
||||||
void clickedPip(PipId pip, bool add);
|
void clickedPip(PipId pip, bool add);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QString movieDir;
|
||||||
|
long currentMovieFrame;
|
||||||
|
long currentFrameSkip;
|
||||||
|
long movieCounter;
|
||||||
|
bool movieSaving;
|
||||||
const float zoomNear_ = 0.05f; // do not zoom closer than this
|
const float zoomNear_ = 0.05f; // do not zoom closer than this
|
||||||
float zoomFar_ = 10.0f; // do not zoom further than this
|
float zoomFar_ = 10.0f; // do not zoom further than this
|
||||||
const float zoomLvl1_ = 1.0f;
|
const float zoomLvl1_ = 1.0f;
|
||||||
|
BIN
gui/resources/camera.png
Normal file
BIN
gui/resources/camera.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
gui/resources/film.png
Normal file
BIN
gui/resources/film.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 653 B |
Loading…
Reference in New Issue
Block a user