Skip to content

Commit

Permalink
Upgrade the save as image function into a background task (#4382)
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn committed Apr 21, 2017
1 parent 20197c2 commit db848a3
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 3 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -95,6 +95,7 @@
%Include qgsmaprendererjob.sip
%Include qgsmaprendererparalleljob.sip
%Include qgsmaprenderersequentialjob.sip
%Include qgsmaprenderertask.sip
%Include qgsmapsettings.sip
%Include qgsmaptopixel.sip
%Include qgsmapunitscale.sip
Expand Down
76 changes: 76 additions & 0 deletions python/core/qgsmaprenderertask.sip
@@ -0,0 +1,76 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsmaprenderertask.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsMapRendererTask : QgsTask
{
%Docstring
QgsTask task which draws a map to an image file or a painter as a background
task. This can be used to draw maps without blocking the QGIS interface.
.. versionadded:: 3.0
%End

%TypeHeaderCode
#include "qgsmaprenderertask.h"
%End
public:

enum ErrorType
{
ImageAllocationFail,
ImageSaveFail
};

QgsMapRendererTask( const QgsMapSettings &ms,
const QString &fileName,
const QString &fileFormat = QString( "PNG" ) );
%Docstring
Constructor for QgsMapRendererTask to render a map to an image file.
%End

QgsMapRendererTask( const QgsMapSettings &ms,
QPainter *p );
%Docstring
Constructor for QgsMapRendererTask to render a map to a painter object.
%End

void addAnnotations( QList< QgsAnnotation * > annotations );
%Docstring
Adds ``annotations`` to be rendered on the map.
%End

signals:

void renderingComplete();
%Docstring
Emitted when the map rendering is successfully completed.
%End

void errorOccurred( int error );
%Docstring
Emitted when map rendering failed.
%End

protected:

virtual bool run();
virtual void finished( bool result );

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsmaprenderertask.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

46 changes: 43 additions & 3 deletions src/app/qgisapp.cpp
Expand Up @@ -270,6 +270,7 @@
#include "qgsvectorlayerutils.h"
#include "qgshelp.h"
#include "qgsvectorfilewritertask.h"
#include "qgsmaprenderertask.h"
#include "qgsnewnamedialog.h"

#include "qgssublayersdialog.h"
Expand Down Expand Up @@ -5772,9 +5773,48 @@ void QgisApp::saveMapAsImage()
QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
if ( myFileNameAndFilter.first != QLatin1String( "" ) )
{
//save the mapview to the selected file
mMapCanvas->saveAsImage( myFileNameAndFilter.first, nullptr, myFileNameAndFilter.second );
statusBar()->showMessage( tr( "Saved map image to %1" ).arg( myFileNameAndFilter.first ) );
//TODO: GUI
int dpi = 90;
QSize size = mMapCanvas->size() * ( dpi / 90 );

QgsMapSettings ms = QgsMapSettings();
ms.setDestinationCrs( QgsProject::instance()->crs() );
ms.setExtent( mMapCanvas->extent() );
ms.setOutputSize( size );
ms.setOutputDpi( dpi );
ms.setBackgroundColor( mMapCanvas->canvasColor() );
ms.setRotation( mMapCanvas->rotation() );
ms.setLayers( mMapCanvas->layers() );

QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, myFileNameAndFilter.first, myFileNameAndFilter.second );
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );

connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ = ]
{
QgsMessageBarItem *successMsg = new QgsMessageBarItem(
tr( "Successfully saved canvas to image" ),
QgsMessageBar::SUCCESS );
messageBar()->pushItem( successMsg );
} );
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ = ]( int error )
{
if ( error == QgsMapRendererTask::ImageAllocationFail )
{
QgsMessageBarItem *errorMsg = new QgsMessageBarItem(
tr( "Could not allocate required memory for image" ),
QgsMessageBar::WARNING );
messageBar()->pushItem( errorMsg );
}
else if ( error == QgsMapRendererTask::ImageAllocationFail )
{
QgsMessageBarItem *errorMsg = new QgsMessageBarItem(
tr( "Could not save the image to file" ),
QgsMessageBar::WARNING );
messageBar()->pushItem( errorMsg );
}
} );

QgsApplication::taskManager()->addTask( mapRendererTask );
}

} // saveMapAsImage
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -173,6 +173,7 @@ SET(QGIS_CORE_SRCS
qgsmaprendererjob.cpp
qgsmaprendererparalleljob.cpp
qgsmaprenderersequentialjob.cpp
qgsmaprenderertask.cpp
qgsmapsettings.cpp
qgsmaptopixel.cpp
qgsmaptopixelgeometrysimplifier.cpp
Expand Down Expand Up @@ -521,6 +522,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsmaprendererjob.h
qgsmaprendererparalleljob.h
qgsmaprenderersequentialjob.h
qgsmaprenderertask.h
qgsmessagelog.h
qgsmessageoutput.h
qgsnetworkaccessmanager.h
Expand Down
133 changes: 133 additions & 0 deletions src/core/qgsmaprenderertask.cpp
@@ -0,0 +1,133 @@
/***************************************************************************
qgsmaprenderertask.h
-------------------------
begin : Apr 2017
copyright : (C) 2017 by Mathieu Pellerin
email : nirvn dot asia 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 "qgsannotation.h"
#include "qgsannotationmanager.h"
#include "qgsmaprenderertask.h"
#include "qgsmaprenderercustompainterjob.h"


QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, const QString &fileFormat )
: QgsTask( tr( "Saving as image" ) )
, mMapSettings( ms )
, mFileName( fileName )
, mFileFormat( fileFormat )
{
}

QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, QPainter *p )
: QgsTask( tr( "Saving as image" ) )
, mMapSettings( ms )
, mPainter( p )
{
}

void QgsMapRendererTask::addAnnotations( QList< QgsAnnotation * > annotations )
{
qDeleteAll( mAnnotations );
mAnnotations.clear();

Q_FOREACH ( const QgsAnnotation *a, annotations )
{
mAnnotations << a->clone();
}
}

bool QgsMapRendererTask::run()
{
QImage img;
std::unique_ptr< QPainter > tempPainter;
QPainter *destPainter = mPainter;

if ( !mPainter )
{
// save rendered map to an image file
img = QImage( mMapSettings.outputSize(), QImage::Format_ARGB32 );
if ( img.isNull() )
{
mError = ImageAllocationFail;
return false;
}

tempPainter.reset( new QPainter( &img ) );
destPainter = tempPainter.get();
}

if ( !destPainter )
return false;

QgsMapRendererCustomPainterJob r( mMapSettings, destPainter );
r.renderSynchronously();

QgsRenderContext context = QgsRenderContext::fromMapSettings( mMapSettings );
context.setPainter( destPainter );

Q_FOREACH ( QgsAnnotation *annotation, mAnnotations )
{
if ( !annotation || !annotation->isVisible() )
{
continue;
}
if ( annotation->mapLayer() && !mMapSettings.layers().contains( annotation->mapLayer() ) )
{
continue;
}

context.painter()->save();
context.painter()->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );

double itemX, itemY;
if ( annotation->hasFixedMapPosition() )
{
itemX = mMapSettings.outputSize().width() * ( annotation->mapPosition().x() - mMapSettings.extent().xMinimum() ) / mMapSettings.extent().width();
itemY = mMapSettings.outputSize().height() * ( 1 - ( annotation->mapPosition().y() - mMapSettings.extent().yMinimum() ) / mMapSettings.extent().height() );
}
else
{
itemX = annotation->relativePosition().x() * mMapSettings.outputSize().width();
itemY = annotation->relativePosition().y() * mMapSettings.outputSize().height();
}

context.painter()->translate( itemX, itemY );

annotation->render( context );
context.painter()->restore();
}
qDeleteAll( mAnnotations );
mAnnotations.clear();

if ( !mFileName.isEmpty() )
{
destPainter->end();
bool success = img.save( mFileName, mFileFormat.toLocal8Bit().data() );
if ( !success )
{
mError = ImageSaveFail;
return false;
}
}

return true;
}

void QgsMapRendererTask::finished( bool result )
{
if ( result )
emit renderingComplete();
else
emit errorOccurred( mError );
}
99 changes: 99 additions & 0 deletions src/core/qgsmaprenderertask.h
@@ -0,0 +1,99 @@
/***************************************************************************
qgsmaprenderertask.h
-------------------------
begin : Apr 2017
copyright : (C) 2017 by Mathieu Pellerin
email : nirvn dot asia 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 QGSMAPRENDERERTASK_H
#define QGSMAPRENDERERTASK_H

#include "qgis.h"
#include "qgis_core.h"
#include "qgsannotation.h"
#include "qgsannotationmanager.h"
#include "qgsmapsettings.h"
#include "qgstaskmanager.h"

#include <QPainter>

/**
* \class QgsMapRendererTask
* \ingroup core
* QgsTask task which draws a map to an image file or a painter as a background
* task. This can be used to draw maps without blocking the QGIS interface.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsMapRendererTask : public QgsTask
{
Q_OBJECT

public:

//! \brief Error type
enum ErrorType
{
ImageAllocationFail = 1, // Image allocation failure
ImageSaveFail // Image save failure
};

/**
* Constructor for QgsMapRendererTask to render a map to an image file.
*/
QgsMapRendererTask( const QgsMapSettings &ms,
const QString &fileName,
const QString &fileFormat = QString( "PNG" ) );

/**
* Constructor for QgsMapRendererTask to render a map to a painter object.
*/
QgsMapRendererTask( const QgsMapSettings &ms,
QPainter *p );

/**
* Adds \a annotations to be rendered on the map.
*/
void addAnnotations( QList< QgsAnnotation * > annotations );

signals:

/**
* Emitted when the map rendering is successfully completed.
*/
void renderingComplete();

/**
* Emitted when map rendering failed.
*/
void errorOccurred( int error );

protected:

virtual bool run() override;
virtual void finished( bool result ) override;

private:

QgsMapSettings mMapSettings;

QPainter *mPainter = nullptr;

QString mFileName;
QString mFileFormat;

QList< QgsAnnotation * > mAnnotations;

int mError = 0;
};

#endif

0 comments on commit db848a3

Please sign in to comment.