diff --git a/python/core/qgsmapsettingsutils.sip b/python/core/qgsmapsettingsutils.sip index d28375f342c..c15613480e2 100644 --- a/python/core/qgsmapsettingsutils.sip +++ b/python/core/qgsmapsettingsutils.sip @@ -22,6 +22,13 @@ class QgsMapSettingsUtils %End public: + static bool containsAdvancedEffects( const QgsMapSettings &mapSettings ); +%Docstring + Checks whether any of the layers attached to a map settings object contain advanced effects + \param mapSettings map settings + :rtype: bool +%End + static QString worldFileContent( const QgsMapSettings &mapSettings ); %Docstring Creates the content of a world file. diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index a99e254bdcc..28b8e5be5a1 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1706,6 +1706,7 @@ void QgisApp::createActions() connect( mActionSaveProject, &QAction::triggered, this, &QgisApp::fileSave ); connect( mActionSaveProjectAs, &QAction::triggered, this, &QgisApp::fileSaveAs ); connect( mActionSaveMapAsImage, &QAction::triggered, this, [ = ] { saveMapAsImage(); } ); + connect( mActionSaveMapAsPdf, &QAction::triggered, this, [ = ] { saveMapAsPdf(); } ); connect( mActionNewMapCanvas, &QAction::triggered, this, &QgisApp::newMapCanvas ); connect( mActionNewPrintComposer, &QAction::triggered, this, &QgisApp::newPrintComposer ); connect( mActionShowComposerManager, &QAction::triggered, this, &QgisApp::showComposerManager ); @@ -2703,6 +2704,7 @@ void QgisApp::setTheme( const QString &themeName ) mActionNewPrintComposer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewComposer.svg" ) ) ); mActionShowComposerManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionComposerManager.svg" ) ) ); mActionSaveMapAsImage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveMapAsImage.svg" ) ) ); + mActionSaveMapAsPdf->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAsPDF.svg" ) ) ); mActionExit->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileExit.png" ) ) ); mActionAddOgrLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOgrLayer.svg" ) ) ); mActionAddRasterLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRasterLayer.svg" ) ) ); @@ -5801,8 +5803,8 @@ void QgisApp::saveMapAsImage() if ( !dlg.exec() ) return; - QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) ); - if ( myFileNameAndFilter.first != QLatin1String( "" ) ) + QPair< QString, QString> fileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) ); + if ( fileNameAndFilter.first != QLatin1String( "" ) ) { QgsMapSettings ms = QgsMapSettings(); ms.setDestinationCrs( QgsProject::instance()->crs() ); @@ -5813,7 +5815,7 @@ void QgisApp::saveMapAsImage() ms.setRotation( mMapCanvas->rotation() ); ms.setLayers( mMapCanvas->layers() ); - QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, myFileNameAndFilter.first, myFileNameAndFilter.second ); + QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second ); if ( dlg.drawAnnotations() ) { @@ -5853,6 +5855,117 @@ void QgisApp::saveMapAsImage() } // saveMapAsImage +void QgisApp::saveMapAsPdf() +{ + QList< QgsMapDecoration * > decorations; + QString activeDecorations; + Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems ) + { + if ( decoration->enabled() ) + { + decorations << decoration; + if ( activeDecorations.isEmpty() ) + activeDecorations = decoration->name().toLower(); + else + activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() ); + } + } + + QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations, QgsMapSaveDialog::Pdf ); + if ( !dlg.exec() ) + return; + + QgsSettings settings; + QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString(); + QString fileName = QFileDialog::getSaveFileName( this, tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" ); + if ( !fileName.isEmpty() ) + { + QgsMapSettings ms = QgsMapSettings(); + ms.setDestinationCrs( QgsProject::instance()->crs() ); + ms.setExtent( dlg.extent() ); + ms.setOutputSize( dlg.size() ); + ms.setOutputDpi( dlg.dpi() ); + ms.setBackgroundColor( mMapCanvas->canvasColor() ); + ms.setRotation( mMapCanvas->rotation() ); + ms.setLayers( mMapCanvas->layers() ); + + QPrinter *printer = new QPrinter(); + printer->setOutputFileName( fileName ); + printer->setOutputFormat( QPrinter::PdfFormat ); + printer->setOrientation( QPrinter::Portrait ); + printer->setPaperSize( dlg.size(), QPrinter::DevicePixel ); + printer->setPageMargins( 0, 0, 0, 0, QPrinter::DevicePixel ); + + QPainter *p = new QPainter(); + QImage *image = nullptr; + if ( dlg.saveAsRaster() ) + { + image = new QImage( dlg.size(), QImage::Format_ARGB32 ); + p->begin( image ); + } + else + { + p->begin( printer ); + } + + QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, p ); + + if ( dlg.drawAnnotations() ) + { + mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() ); + } + + if ( dlg.drawDecorations() ) + { + mapRendererTask->addDecorations( decorations ); + } + + mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() ); + + connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ this, p, image, printer ] + { + messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) ); + p->end(); + + if ( image ) + { + QPainter pp; + pp.begin( printer ); + QRectF rect( 0, 0, image->width(), image->height() ); + pp.drawImage( rect, *image, rect ); + pp.end(); + } + + delete p; + delete image; + delete printer; + } ); + connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ this, p, image, printer ]( int error ) + { + switch ( error ) + { + case QgsMapRendererTask::ImageAllocationFail: + { + messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) ); + break; + } + case QgsMapRendererTask::ImageSaveFail: + { + messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the image to file" ) ); + break; + } + } + + delete p; + delete image; + delete printer; + } ); + + QgsApplication::taskManager()->addTask( mapRendererTask ); + } + +} // saveMapAsPdf + //overloaded version of the above function void QgisApp::saveMapAsImage( const QString &imageFileNameQString, QPixmap *theQPixmap ) { diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 86c3d439cf1..e93e0b64bf1 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -391,6 +391,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow QAction *actionSaveProject() { return mActionSaveProject; } QAction *actionSaveProjectAs() { return mActionSaveProjectAs; } QAction *actionSaveMapAsImage() { return mActionSaveMapAsImage; } + QAction *actionSaveMapAsPdf() { return mActionSaveMapAsPdf; } QAction *actionProjectProperties() { return mActionProjectProperties; } QAction *actionShowComposerManager() { return mActionShowComposerManager; } QAction *actionNewPrintComposer() { return mActionNewPrintComposer; } @@ -1080,6 +1081,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void runScript( const QString &filePath ); //! Save the map view as an image - user is prompted for image name using a dialog void saveMapAsImage(); + //! Save the map view as a pdf - user is prompted for image name using a dialog + void saveMapAsPdf(); //! Open a project void fileOpen(); //! Create a new project diff --git a/src/app/qgsmapsavedialog.cpp b/src/app/qgsmapsavedialog.cpp index 4f780ddf9c8..b23fdd284a6 100644 --- a/src/app/qgsmapsavedialog.cpp +++ b/src/app/qgsmapsavedialog.cpp @@ -22,6 +22,7 @@ #include "qgsdecorationitem.h" #include "qgsextentgroupbox.h" #include "qgsmapsettings.h" +#include "qgsmapsettingsutils.h" #include #include @@ -29,7 +30,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); -QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations ) +QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations, DialogType type ) : QDialog( parent ) { setupUi( this ); @@ -60,6 +61,16 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale ); updateOutputSize(); + + if ( type == QgsMapSaveDialog::Pdf ) + { + mSaveWorldFile->setVisible( false ); + + mSaveAsRaster->setChecked( QgsMapSettingsUtils::containsAdvancedEffects( mapCanvas->mapSettings() ) ); + mSaveAsRaster->setVisible( true ); + + this->setWindowTitle( tr( "Save map as PDF" ) ); + } } void QgsMapSaveDialog::updateDpi( int dpi ) @@ -152,3 +163,8 @@ bool QgsMapSaveDialog::saveWorldFile() const { return mSaveWorldFile->isChecked(); } + +bool QgsMapSaveDialog::saveAsRaster() const +{ + return mSaveAsRaster->isChecked(); +} diff --git a/src/app/qgsmapsavedialog.h b/src/app/qgsmapsavedialog.h index 384db60ab78..9066e159c87 100644 --- a/src/app/qgsmapsavedialog.h +++ b/src/app/qgsmapsavedialog.h @@ -37,9 +37,15 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog public: + enum DialogType + { + Image = 1, // Image-specific dialog + Pdf // PDF-specific dialog + }; + /** Constructor for QgsMapSaveDialog */ - QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString() ); + QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString(), DialogType type = Image ); //! returns extent rectangle QgsRectangle extent() const; @@ -59,6 +65,9 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog //! returns whether a world file will be created bool saveWorldFile() const; + //! returns whether the map will be rasterized + bool saveAsRaster() const; + private: void updateDpi( int dpi ); diff --git a/src/core/composer/qgscomposermap.cpp b/src/core/composer/qgscomposermap.cpp index 5f4243e0f58..e064cf6cc0c 100644 --- a/src/core/composer/qgscomposermap.cpp +++ b/src/core/composer/qgscomposermap.cpp @@ -25,6 +25,7 @@ #include "qgsmaplayerlistutils.h" #include "qgsmaplayerstylemanager.h" #include "qgsmaptopixel.h" +#include "qgsmapsettingsutils.h" #include "qgspainting.h" #include "qgsproject.h" #include "qgsrasterdataprovider.h" @@ -1071,43 +1072,10 @@ bool QgsComposerMap::containsAdvancedEffects() const return true; } - // check if map contains advanced effects like blend modes, or flattened layers for transparency - QgsTextFormat layerFormat; - Q_FOREACH ( QgsMapLayer *layer, layersToRender() ) - { - if ( layer ) - { - if ( layer->blendMode() != QPainter::CompositionMode_SourceOver ) - { - return true; - } - // if vector layer, check labels and feature blend mode - QgsVectorLayer *currentVectorLayer = qobject_cast( layer ); - if ( currentVectorLayer ) - { - if ( currentVectorLayer->layerTransparency() != 0 ) - { - return true; - } - if ( currentVectorLayer->featureBlendMode() != QPainter::CompositionMode_SourceOver ) - { - return true; - } - // check label blend modes - if ( QgsPalLabeling::staticWillUseLayer( currentVectorLayer ) ) - { - // Check all label blending properties - - layerFormat.readFromLayer( currentVectorLayer ); - if ( layerFormat.containsAdvancedEffects() ) - return true; - } - } - } - } - - return false; + QgsMapSettings ms; + ms.setLayers( layersToRender() ); + return QgsMapSettingsUtils::containsAdvancedEffects( ms ); } void QgsComposerMap::connectUpdateSlot() diff --git a/src/core/qgsmapsettingsutils.cpp b/src/core/qgsmapsettingsutils.cpp index 61a1ed885ce..df14aa73eb8 100644 --- a/src/core/qgsmapsettingsutils.cpp +++ b/src/core/qgsmapsettingsutils.cpp @@ -17,9 +17,49 @@ #include "qgsmapsettings.h" #include "qgsmapsettingsutils.h" +#include "qgspallabeling.h" +#include "qgstextrenderer.h" +#include "qgsvectorlayer.h" #include +bool QgsMapSettingsUtils::containsAdvancedEffects( const QgsMapSettings &mapSettings ) +{ + QgsTextFormat layerFormat; + Q_FOREACH ( QgsMapLayer *layer, mapSettings.layers() ) + { + if ( layer ) + { + if ( layer->blendMode() != QPainter::CompositionMode_SourceOver ) + { + return true; + } + // if vector layer, check labels and feature blend mode + QgsVectorLayer *currentVectorLayer = qobject_cast( layer ); + if ( currentVectorLayer ) + { + if ( currentVectorLayer->layerTransparency() != 0 ) + { + return true; + } + if ( currentVectorLayer->featureBlendMode() != QPainter::CompositionMode_SourceOver ) + { + return true; + } + // check label blend modes + if ( QgsPalLabeling::staticWillUseLayer( currentVectorLayer ) ) + { + // Check all label blending properties + layerFormat.readFromLayer( currentVectorLayer ); + if ( layerFormat.containsAdvancedEffects() ) + return true; + } + } + } + } + return false; +} + QString QgsMapSettingsUtils::worldFileContent( const QgsMapSettings &mapSettings ) { QgsMapSettings ms = mapSettings; diff --git a/src/core/qgsmapsettingsutils.h b/src/core/qgsmapsettingsutils.h index bf318be0657..e3ece498f8e 100644 --- a/src/core/qgsmapsettingsutils.h +++ b/src/core/qgsmapsettingsutils.h @@ -32,6 +32,11 @@ class CORE_EXPORT QgsMapSettingsUtils public: + /** Checks whether any of the layers attached to a map settings object contain advanced effects + * \param mapSettings map settings + */ + static bool containsAdvancedEffects( const QgsMapSettings &mapSettings ); + /** Creates the content of a world file. * \param mapSettings map settings * \note Uses 17 places of precision for all numbers output diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 64f4a59f87c..910c1386a91 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -50,6 +50,7 @@ + @@ -640,7 +641,16 @@ :/images/themes/default/mActionSaveMapAsImage.svg:/images/themes/default/mActionSaveMapAsImage.svg - Save as &Image... + Save Map as &Image... + + + + + + :/images/themes/default/mActionSaveAsPDF.svg:/images/themes/default/mActionSaveAsPDF.svg + + + Save Map as &PDF... diff --git a/src/ui/qgsmapsavedialog.ui b/src/ui/qgsmapsavedialog.ui index a93c2b1344d..e8b66184be2 100644 --- a/src/ui/qgsmapsavedialog.ui +++ b/src/ui/qgsmapsavedialog.ui @@ -16,6 +16,23 @@ + + + + Rasterize map + + + Advanced effects such as blend modes or vector layer transparency cannot be exported as vectors. +Rasterizing the map is recommended when such effects are used. + + + false + + + false + + +