Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3d] export all frames from QGIS 3d animations as images #9244

Merged
merged 5 commits into from
Feb 27, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/3d/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
SET(QGIS_3D_SRCS
qgsaabb.cpp
qgsabstract3dengine.cpp
qgs3danimationsettings.cpp
qgs3dmapscene.cpp
qgs3dmapsettings.cpp
qgs3dutils.cpp
Expand Down Expand Up @@ -90,6 +91,7 @@ QT5_ADD_RESOURCES(QGIS_3D_RCC_SRCS shaders.qrc)
SET(QGIS_3D_HDRS
qgsaabb.h
qgsabstract3dengine.h
qgs3danimationsettings.h
qgs3dmapscene.h
qgs3dmapsettings.h
qgs3dtypes.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#define QGS3DANIMATIONSETTINGS_H

#include "qgsvector3d.h"
#include "qgis_3d.h"

#include <QEasingCurve>
#include <QVector>
Expand All @@ -28,8 +29,9 @@ class QgsReadWriteContext;
/**
* Class that holds information about animation in 3D view. The animation is defined
* as a series of keyframes
* \since QGIS 3.8
*/
class Qgs3DAnimationSettings
class _3D_EXPORT Qgs3DAnimationSettings
{
public:
Qgs3DAnimationSettings() = default;
Expand Down
91 changes: 90 additions & 1 deletion src/3d/qgs3dutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@
#include "qgsabstractgeometry.h"
#include "qgsvectorlayer.h"
#include "qgsexpressioncontextutils.h"
#include "qgsfeedback.h"
#include "qgsexpression.h"
#include "qgsexpressionutils.h"
#include "qgsoffscreen3dengine.h"

#include "qgs3dmapscene.h"
#include "qgsabstract3dengine.h"
#include "qgsterraingenerator.h"
#include "qgscameracontroller.h"

#include "qgsline3dsymbol.h"
#include "qgspoint3dsymbol.h"
#include "qgspolygon3dsymbol.h"

#include <Qt3DExtras/QPhongMaterial>


QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene )
{
QImage resImage;
Expand Down Expand Up @@ -76,6 +80,91 @@ QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene
return resImage;
}

bool Qgs3DUtils::exportAnimation( const Qgs3DAnimationSettings &animationSettings,
const Qgs3DMapSettings &mapSettings,
int framesPerSecond,
const QString &outputDirectory,
const QString &fileNameTemplate,
const QSize &outputSize,
QString &error,
QgsFeedback *feedback
)
{
QgsOffscreen3DEngine engine;
engine.setSize( outputSize );
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
engine.setRootEntity( scene );

if ( animationSettings.keyFrames().size() < 2 )
{
error = QObject::tr( "Unable to export 3D animation. Add at least 2 keyframes" );
return false;
}

const float duration = animationSettings.duration(); //in seconds
if ( duration <= 0 )
{
error = QObject::tr( "Unable to export 3D animation (invalid duration)." );
return false;
}

float time = 0;
int frameNo = 0;
int totalFrames = static_cast<int>( duration * framesPerSecond );

if ( fileNameTemplate.isEmpty() )
{
error = QObject::tr( "Filename template is empty" );
return false;
}

int numberOfDigits = fileNameTemplate.count( QLatin1Char( '#' ) );
if ( numberOfDigits < 0 )
{
error = QObject::tr( "Wrong filename template format (must contain #)" );
return false;
}
const QString token( numberOfDigits, QLatin1Char( '#' ) );
if ( !fileNameTemplate.contains( token ) )
{
error = QObject::tr( "Filename template must contain all # placeholders in one continuous group." );
return false;
}

while ( time <= duration )
{

if ( feedback )
{
if ( feedback->isCanceled() )
return true;
PeterPetrik marked this conversation as resolved.
Show resolved Hide resolved
feedback->setProgress( frameNo / static_cast<double>( totalFrames ) * 100 );
QCoreApplication::processEvents();
PeterPetrik marked this conversation as resolved.
Show resolved Hide resolved
}
++frameNo;

Qgs3DAnimationSettings::Keyframe kf = animationSettings.interpolate( time );
scene->cameraController()->setLookingAtPoint( kf.point, kf.dist, kf.pitch, kf.yaw );

QString fileName( fileNameTemplate );
const QString frameNoPaddedLeft( QStringLiteral( "%1" ).arg( frameNo, numberOfDigits, 10, QChar( '0' ) ) ); // e.g. 0001
fileName.replace( token, frameNoPaddedLeft );
const QString path = QDir( outputDirectory ).filePath( fileName );

// It would initially return empty rendered image.
// Capturing the initial image and throwing it away fixes that.
// Hopefully we will find a better fix in the future.
Qgs3DUtils::captureSceneImage( engine, scene );
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );

img.save( path );

time += 1.0f / static_cast<float>( framesPerSecond );
}

return true;
}


int Qgs3DUtils::maxZoomLevel( double tile0width, double tileResolution, double maxError )
{
Expand Down
30 changes: 30 additions & 0 deletions src/3d/qgs3dutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

class QgsLineString;
class QgsPolygon;
class QgsFeedback;

class QgsAbstract3DEngine;
class QgsAbstract3DSymbol;
Expand All @@ -31,6 +32,7 @@ namespace Qt3DExtras
}

#include "qgs3dmapsettings.h"
#include "qgs3danimationsettings.h"
#include "qgs3dtypes.h"
#include "qgsaabb.h"

Expand All @@ -54,6 +56,34 @@ class _3D_EXPORT Qgs3DUtils
*/
static QImage captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene );

/**
* Captures 3D animation frames to the selected folder
*
* \param animationSettings Settings for keyframes and camera
* \param mapSettings 3d map settings
* \param framesPerSecond number of frames per second to export
* \param outputDirectory output directory where to export frames
* \param fileNameTemplate template for exporting the frames.
* Must be in format prefix####.<format>, where number of
* # represents how many 0 should be left-padded to the frame number
* e.g. my###.jpg will create frames my001.jpg, my002.jpg, etc
* \param outputSize size of the frame in pixels
* \param error error string in case of failure
* \param feedback optional feedback object used to cancel export or report progress
* \return whether export succeeded. In case of failure, see error argument
*
* \since QGIS 3.8
*/
static bool exportAnimation( const Qgs3DAnimationSettings &animationSettings,
const Qgs3DMapSettings &mapSettings,
int framesPerSecond,
const QString &outputDirectory,
const QString &fileNameTemplate,
const QSize &outputSize,
QString &error,
QgsFeedback *feedback = nullptr
);

/**
* Calculates the highest needed zoom level for tiles in quad-tree given width of the base tile (zoom level 0)
* in map units, resolution of the tile (e.g. tile's texture width) and desired maximum error in map units.
Expand Down
110 changes: 110 additions & 0 deletions src/app/3d/qgs3danimationexportdialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/***************************************************************************
qgs3danimationexportdialog.cpp
------------------------------
Date : February 2019
Copyright : (C) 2019 by Peter Petrik
Email : zilolv at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgs3danimationexportdialog.h"
#include "qgsproject.h"
#include "qgsgui.h"
#include "qgssettings.h"
#include "qgsoffscreen3dengine.h"
#include "qgs3danimationsettings.h"
#include "qgs3dmapsettings.h"
#include "qgs3dmapscene.h"
#include "qgs3dutils.h"
#include "qgscameracontroller.h"
#include "qgsspinbox.h"

#include <QtGlobal>

Qgs3DAnimationExportDialog::Qgs3DAnimationExportDialog(): QDialog( nullptr )
{
setupUi( this );
QgsSettings settings;

const QString templateText = settings.value( QStringLiteral( "Export3DAnimation/fileNameTemplate" ),
QStringLiteral( "%1####.jpg" ).arg( QgsProject::instance()->baseName() )
, QgsSettings::App ).toString();
mTemplateLineEdit->setText( templateText );
QRegExp rx( QStringLiteral( "\\w+#+\\.{1}\\w+" ) ); //e.g. anyprefix#####.png
QValidator *validator = new QRegExpValidator( rx, this );
mTemplateLineEdit->setValidator( validator );

connect( mTemplateLineEdit, &QLineEdit::textChanged, this, [ = ]
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Export3DAnimation/fileNameTemplate" ), mTemplateLineEdit->text() );
} );

mOutputDirFileWidget->setStorageMode( QgsFileWidget::GetDirectory );
mOutputDirFileWidget->setDialogTitle( tr( "Select directory for 3D animation frames" ) );
mOutputDirFileWidget->lineEdit()->setShowClearButton( false );
mOutputDirFileWidget->setDefaultRoot( settings.value( QStringLiteral( "Export3DAnimation/lastDir" ), QString(), QgsSettings::App ).toString() );
mOutputDirFileWidget->setFilePath( settings.value( QStringLiteral( "Export3DAnimation/lastDir" ), QString(), QgsSettings::App ).toString() );

connect( mOutputDirFileWidget, &QgsFileWidget::fileChanged, this, [ = ]
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Export3DAnimation/lastDir" ), mOutputDirFileWidget->filePath(), QgsSettings::App );
} );

mFpsSpinBox->setValue( settings.value( QStringLiteral( "Export3DAnimation/fps" ), 30 ).toInt() );
connect( mFpsSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QgsSpinBox::valueChanged ), this, [ = ]
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Export3DAnimation/fps" ), mFpsSpinBox->value() );
} );

mWidthSpinBox->setValue( settings.value( QStringLiteral( "Export3DAnimation/width" ), 800 ).toInt() );
connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QgsSpinBox::valueChanged ), this, [ = ]
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Export3DAnimation/width" ), mWidthSpinBox->value() );
} );

mHeightSpinBox->setValue( settings.value( QStringLiteral( "Export3DAnimation/height" ), 600 ).toInt() );
connect( mHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QgsSpinBox::valueChanged ), this, [ = ]
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Export3DAnimation/height" ), mHeightSpinBox->value() );
} );

QgsGui::enableAutoGeometryRestore( this );
}

QString Qgs3DAnimationExportDialog::outputDirectory() const
{
const QString dir = mOutputDirFileWidget->filePath();
return dir;
}

QString Qgs3DAnimationExportDialog::fileNameExpression() const
{
const QString name = mTemplateLineEdit->text();
return name;
}

Qgs3DAnimationExportDialog::~Qgs3DAnimationExportDialog() = default;

int Qgs3DAnimationExportDialog::fps() const
{
const int fps = mFpsSpinBox->value();
return fps;
}

QSize Qgs3DAnimationExportDialog::frameSize() const
{
const int width = mWidthSpinBox->value();
const int height = mHeightSpinBox->value();
return QSize( width, height );
}
51 changes: 51 additions & 0 deletions src/app/3d/qgs3danimationexportdialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/***************************************************************************
qgs3danimationexportdialog.h
----------------------------
Date : February 2019
Copyright : (C) 2019 by Peter Petrik
Email : zilolv at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGS3DANIMATIONEXPORTDIALOG_H
#define QGS3DANIMATIONEXPORTDIALOG_H

#include <QWidget>
#include <memory>
#include <QSize>

#include "qgs3dmapsettings.h"
#include "qgs3danimationsettings.h"

#include "ui_animationexport3ddialog.h"

/**
* Dialog for settings for 3D animation export
*/
class Qgs3DAnimationExportDialog : public QDialog, private Ui::AnimationExport3DDialog
{
Q_OBJECT
public:
explicit Qgs3DAnimationExportDialog();
~Qgs3DAnimationExportDialog() override;

//! Returns output directory for frames
QString outputDirectory( ) const;

//! Returns filename template for frames
QString fileNameExpression( ) const;

//! Returns frames per second
int fps() const;

//! Returns size of frame in pixels
QSize frameSize() const;
};

#endif // QGS3DANIMATIONEXPORTDIALOG_H
Loading