Skip to content
Permalink
Browse files

[layouts] Resurrect action for exporting to raster images

...this time, without all the useful code locked away in app!
  • Loading branch information
nyalldawson committed Dec 11, 2017
1 parent 113664f commit 953d2c437d11e2bbf1c9eba16e67a592d8c63321
@@ -185,6 +185,7 @@ SET(QGIS_APP_SRCS
layout/qgslayoutdesignerdialog.cpp
layout/qgslayoutguidewidget.cpp
layout/qgslayouthtmlwidget.cpp
layout/qgslayoutimageexportoptionsdialog.cpp
layout/qgslayoutitemslistview.cpp
layout/qgslayoutappmenuprovider.cpp
layout/qgslayoutlabelwidget.cpp
@@ -403,6 +404,7 @@ SET (QGIS_APP_MOC_HDRS
layout/qgslayoutdesignerdialog.h
layout/qgslayoutguidewidget.h
layout/qgslayouthtmlwidget.h
layout/qgslayoutimageexportoptionsdialog.h
layout/qgslayoutitemslistview.h
layout/qgslayoutlabelwidget.h
layout/qgslayoutlegendwidget.h
@@ -33,6 +33,9 @@
#include "qgslayoutviewtoolselect.h"
#include "qgslayoutviewtooleditnodes.h"
#include "qgslayoutitemwidget.h"
#include "qgslayoutimageexportoptionsdialog.h"
#include "qgslayoutitemmap.h"
#include "qgsmessageviewer.h"
#include "qgsgui.h"
#include "qgslayoutitemguiregistry.h"
#include "qgslayoutpropertieswidget.h"
@@ -168,6 +171,8 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionLayoutManager, &QAction::triggered, this, &QgsLayoutDesignerDialog::showManager );
connect( mActionRemoveLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::deleteLayout );

connect( mActionExportAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToRaster );

connect( mActionShowGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::showGrid );
connect( mActionSnapGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToGrid );

@@ -1410,6 +1415,123 @@ void QgsLayoutDesignerDialog::deleteLayout()
close();
}

void QgsLayoutDesignerDialog::exportToRaster()
{
if ( containsWmsLayers() )
showWmsPrintingWarning();

// Image size
double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
QSizeF maxPageSize = mLayout->pageCollection()->maximumPageSize();
bool hasUniformPageSizes = mLayout->pageCollection()->hasUniformPageSizes();
int width = ( int )( mLayout->context().dpi() * maxPageSize.width() / oneInchInLayoutUnits );
int height = ( int )( mLayout->context().dpi() * maxPageSize.height() / oneInchInLayoutUnits );
double dpi = mLayout->context().dpi();

int memuse = width * height * 3 / 1000000; // pixmap + image
QgsDebugMsg( QString( "Image %1x%2" ).arg( width ).arg( height ) );
QgsDebugMsg( QString( "memuse = %1" ).arg( memuse ) );

if ( memuse > 400 ) // about 4500x4500
{
int answer = QMessageBox::warning( nullptr, tr( "Export layout" ),
tr( "To create an image of %1x%2 requires about %3 MB of memory. Proceed?" )
.arg( width ).arg( height ).arg( memuse ),
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok );

raise();
if ( answer == QMessageBox::Cancel )
return;
}

//get some defaults from the composition
bool cropToContents = mLayout->customProperty( QStringLiteral( "imageCropToContents" ), false ).toBool();
int marginTop = mLayout->customProperty( QStringLiteral( "imageCropMarginTop" ), 0 ).toInt();
int marginRight = mLayout->customProperty( QStringLiteral( "imageCropMarginRight" ), 0 ).toInt();
int marginBottom = mLayout->customProperty( QStringLiteral( "imageCropMarginBottom" ), 0 ).toInt();
int marginLeft = mLayout->customProperty( QStringLiteral( "imageCropMarginLeft" ), 0 ).toInt();

QgsLayoutImageExportOptionsDialog imageDlg( this );
imageDlg.setImageSize( maxPageSize );
imageDlg.setResolution( dpi );
imageDlg.setCropToContents( cropToContents );
imageDlg.setCropMargins( marginTop, marginRight, marginBottom, marginLeft );

#if 0 //TODO
QgsAtlasComposition *atlasMap = &mComposition->atlasComposition();
#endif

QString outputFileName;
#if 0 //TODO
if ( atlasMap->enabled() && mComposition->atlasMode() == QgsComposition::PreviewAtlas )
{
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
outputFileName = QDir( lastUsedDir ).filePath( atlasMap->currentFilename() );
}
#endif

#ifdef Q_OS_MAC
mQgis->activateWindow();
this->raise();
#endif
QPair<QString, QString> fileNExt = QgsGuiUtils::getSaveAsImageName( this, tr( "Save layout as" ), outputFileName );
this->activateWindow();

if ( fileNExt.first.isEmpty() )
{
return;
}

if ( !imageDlg.exec() )
return;

cropToContents = imageDlg.cropToContents();
imageDlg.getCropMargins( marginTop, marginRight, marginBottom, marginLeft );
mLayout->setCustomProperty( QStringLiteral( "imageCropToContents" ), cropToContents );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginTop" ), marginTop );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginRight" ), marginRight );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginBottom" ), marginBottom );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginLeft" ), marginLeft );

mView->setPaintingEnabled( false );

QgsLayoutExporter exporter( mLayout );

QgsLayoutExporter::ImageExportSettings settings;
settings.cropToContents = cropToContents;
settings.cropMargins = QgsMargins( marginLeft, marginTop, marginRight, marginBottom );
settings.dpi = imageDlg.resolution();
if ( hasUniformPageSizes )
{
settings.imageSize = QSize( imageDlg.imageWidth(), imageDlg.imageHeight() );
}
settings.generateWorldFile = mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool();

switch ( exporter.exportToImage( fileNExt.first, settings ) )
{
case QgsLayoutExporter::Success:
break;

case QgsLayoutExporter::FileError:
QMessageBox::warning( this, tr( "Image Export Error" ),
QString( tr( "Cannot write to %1.\n\nThis file may be open in another application." ) ).arg( exporter.errorFile() ),
QMessageBox::Ok,
QMessageBox::Ok );
break;

case QgsLayoutExporter::MemoryError:
QMessageBox::warning( nullptr, tr( "Memory Allocation Error" ),
tr( "Trying to create image %1 (%2×%3 @ %4dpi ) "
"resulted in a memory overflow.\n\n"
"Please try a lower resolution or a smaller paper size." )
.arg( exporter.errorFile() ).arg( imageDlg.imageWidth() ).arg( imageDlg.imageHeight() ).arg( settings.dpi ),
QMessageBox::Ok, QMessageBox::Ok );
break;

}
mView->setPaintingEnabled( true );
}

void QgsLayoutDesignerDialog::paste()
{
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
@@ -1525,6 +1647,36 @@ void QgsLayoutDesignerDialog::initializeRegistry()

}

bool QgsLayoutDesignerDialog::containsWmsLayers() const
{
QList< QgsLayoutItemMap *> maps;
mLayout->layoutItems( maps );

for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
{
if ( map->containsWmsLayer() )
return true;
}
return false;
}

void QgsLayoutDesignerDialog::showWmsPrintingWarning()
{
QgsSettings settings;
bool displayWMSWarning = settings.value( QStringLiteral( "/UI/displayComposerWMSWarning" ), true ).toBool();
if ( displayWMSWarning )
{
QgsMessageViewer *m = new QgsMessageViewer( this );
m->setWindowTitle( tr( "Project Contains WMS Layers" ) );
m->setMessage( tr( "Some WMS servers (e.g. UMN mapserver) have a limit for the WIDTH and HEIGHT parameter. Printing layers from such servers may exceed this limit. If this is the case, the WMS layer will not be printed" ), QgsMessageOutput::MessageText );
m->setCheckBoxText( tr( "Don't show this message again" ) );
m->setCheckBoxState( Qt::Unchecked );
m->setCheckBoxVisible( true );
m->setCheckBoxQgsSettingsLabel( QStringLiteral( "/UI/displayComposerWMSWarning" ) );
m->exec(); //deleted on close
}
}

void QgsLayoutDesignerDialog::selectItems( const QList<QgsLayoutItem *> items )
{
for ( QGraphicsItem *item : items )
@@ -274,6 +274,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void showManager();
void renameLayout();
void deleteLayout();
void exportToRaster();

private:

@@ -360,6 +361,10 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner

void initializeRegistry();

bool containsWmsLayers() const;

//! Displays a warning because of possible min/max size in WMS
void showWmsPrintingWarning();
};

#endif // QGSLAYOUTDESIGNERDIALOG_H
@@ -0,0 +1,167 @@
/***************************************************************************
qgslayoutimageexportoptionsdialog.cpp
-------------------------------------
begin : December 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson 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 "qgslayoutimageexportoptionsdialog.h"
#include "qgis.h"
#include "qgssettings.h"
#include "qgsgui.h"

#include <QCheckBox>
#include <QPushButton>

QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *parent, Qt::WindowFlags flags )
: QDialog( parent, flags )
{
setupUi( this );
connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged );
connect( mHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged );
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged );

connect( mClipToContentGroupBox, &QGroupBox::toggled, this, &QgsLayoutImageExportOptionsDialog::clipToContentsToggled );

QgsGui::instance()->enableAutoGeometryRestore( this );
}

void QgsLayoutImageExportOptionsDialog::setResolution( double resolution )
{
mResolutionSpinBox->setValue( resolution );

if ( mImageSize.isValid() )
{
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
if ( mClipToContentGroupBox->isChecked() )
{
mWidthSpinBox->setValue( 0 );
mHeightSpinBox->setValue( 0 );
}
else
{
mWidthSpinBox->setValue( mImageSize.width() * resolution / 25.4 );
mHeightSpinBox->setValue( mImageSize.height() * resolution / 25.4 );
}
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}
}

double QgsLayoutImageExportOptionsDialog::resolution() const
{
return mResolutionSpinBox->value();
}

void QgsLayoutImageExportOptionsDialog::setImageSize( QSizeF size )
{
mImageSize = size;
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
mWidthSpinBox->setValue( size.width() * mResolutionSpinBox->value() / 25.4 );
mHeightSpinBox->setValue( size.height() * mResolutionSpinBox->value() / 25.4 );
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}

int QgsLayoutImageExportOptionsDialog::imageWidth() const
{
return mWidthSpinBox->value();
}

int QgsLayoutImageExportOptionsDialog::imageHeight() const
{
return mHeightSpinBox->value();
}

void QgsLayoutImageExportOptionsDialog::setCropToContents( bool crop )
{
mClipToContentGroupBox->setChecked( crop );
}

bool QgsLayoutImageExportOptionsDialog::cropToContents() const
{
return mClipToContentGroupBox->isChecked();
}

void QgsLayoutImageExportOptionsDialog::getCropMargins( int &topMargin, int &rightMargin, int &bottomMargin, int &leftMargin ) const
{
topMargin = mTopMarginSpinBox->value();
rightMargin = mRightMarginSpinBox->value();
bottomMargin = mBottomMarginSpinBox->value();
leftMargin = mLeftMarginSpinBox->value();
}

void QgsLayoutImageExportOptionsDialog::setCropMargins( int topMargin, int rightMargin, int bottomMargin, int leftMargin )
{
mTopMarginSpinBox->setValue( topMargin );
mRightMarginSpinBox->setValue( rightMargin );
mBottomMarginSpinBox->setValue( bottomMargin );
mLeftMarginSpinBox->setValue( leftMargin );
}

void QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged( int value )
{
mHeightSpinBox->blockSignals( true );
mResolutionSpinBox->blockSignals( true );
mHeightSpinBox->setValue( mImageSize.height() * value / mImageSize.width() );
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.width() );
mHeightSpinBox->blockSignals( false );
mResolutionSpinBox->blockSignals( false );
}

void QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged( int value )
{
mWidthSpinBox->blockSignals( true );
mResolutionSpinBox->blockSignals( true );
mWidthSpinBox->setValue( mImageSize.width() * value / mImageSize.height() );
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.height() );
mWidthSpinBox->blockSignals( false );
mResolutionSpinBox->blockSignals( false );
}

void QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged( int value )
{
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
if ( mClipToContentGroupBox->isChecked() )
{
mWidthSpinBox->setValue( 0 );
mHeightSpinBox->setValue( 0 );
}
else
{
mWidthSpinBox->setValue( mImageSize.width() * value / 25.4 );
mHeightSpinBox->setValue( mImageSize.height() * value / 25.4 );
}
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}

void QgsLayoutImageExportOptionsDialog::clipToContentsToggled( bool state )
{
mWidthSpinBox->setEnabled( !state );
mHeightSpinBox->setEnabled( !state );

if ( state )
{
whileBlocking( mWidthSpinBox )->setValue( 0 );
whileBlocking( mHeightSpinBox )->setValue( 0 );
}
else
{
whileBlocking( mWidthSpinBox )->setValue( mImageSize.width() * mResolutionSpinBox->value() / 25.4 );
whileBlocking( mHeightSpinBox )->setValue( mImageSize.height() * mResolutionSpinBox->value() / 25.4 );
}
}

0 comments on commit 953d2c4

Please sign in to comment.
You can’t perform that action at this time.