Skip to content
Permalink
Browse files

[feature][layouts] Expose option to control PDF image compression

method when exporting layouts to PDF

Options are for Lossy compression, which is the default JPEG compression
used, and Lossless compression (which creates bigger files in most
cases, but is much more suitable for professional printing outputs
or for post-production in Illustrator/etc)

The Bad news: this option is available in builds based on Qt 5.14
or later.
  • Loading branch information
nyalldawson committed Jul 5, 2020
1 parent 457a526 commit b2cef7377cfcae3ccb70b89921f456b0d2403ce9
@@ -32,6 +32,7 @@ Stores information relating to the current rendering settings for a layout.
FlagDrawSelection,
FlagDisableTiledRasterLayerRenders,
FlagRenderLabelsByMapLayer,
FlagLosslessImageRendering,
};
typedef QFlags<QgsLayoutRenderContext::Flag> Flags;

@@ -345,6 +345,7 @@ Gets color that is used for drawing of selected vector features
RenderPartialOutput,
RenderPreviewJob,
RenderBlocking,
LosslessImageRendering,
// TODO: ignore scale-based visibility (overview)
};
typedef QFlags<QgsMapSettings::Flag> Flags;
@@ -44,6 +44,7 @@ to be rendered etc.
RenderPreviewJob,
RenderBlocking,
RenderSymbolPreview,
LosslessImageRendering,
};
typedef QFlags<QgsRenderContext::Flag> Flags;

@@ -4338,12 +4338,14 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
bool simplify = true;
bool geoPdf = false;
bool useOgcBestPracticeFormat = false;
bool losslessImages = false;
QStringList exportThemes;
QStringList geoPdfLayerOrder;
if ( mLayout )
{
settings.flags = mLayout->renderContext().flags();
forceVector = mLayout->customProperty( QStringLiteral( "forceVector" ), 0 ).toBool();
losslessImages = mLayout->customProperty( QStringLiteral( "pdfLosslessImages" ), 0 ).toBool();
appendGeoreference = mLayout->customProperty( QStringLiteral( "pdfAppendGeoreference" ), 1 ).toBool();
includeMetadata = mLayout->customProperty( QStringLiteral( "pdfIncludeMetadata" ), 1 ).toBool();
disableRasterTiles = mLayout->customProperty( QStringLiteral( "pdfDisableRasterTiles" ), 0 ).toBool();
@@ -4400,6 +4402,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
dialog.setExportGeoPdf( geoPdf );
dialog.setUseOgcBestPracticeFormat( useOgcBestPracticeFormat );
dialog.setExportThemes( exportThemes );
dialog.setLosslessImageExport( losslessImages );

if ( dialog.exec() != QDialog::Accepted )
return false;
@@ -4414,6 +4417,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
useOgcBestPracticeFormat = dialog.useOgcBestPracticeFormat();
exportThemes = dialog.exportThemes();
geoPdfLayerOrder = dialog.geoPdfLayerOrder();
losslessImages = dialog.losslessImageExport();

if ( mLayout )
{
@@ -4428,6 +4432,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
mLayout->setCustomProperty( QStringLiteral( "pdfOgcBestPracticeFormat" ), useOgcBestPracticeFormat ? 1 : 0 );
mLayout->setCustomProperty( QStringLiteral( "pdfExportThemes" ), exportThemes.join( QStringLiteral( "~~~" ) ) );
mLayout->setCustomProperty( QStringLiteral( "pdfLayerOrder" ), geoPdfLayerOrder.join( QStringLiteral( "~~~" ) ) );
mLayout->setCustomProperty( QStringLiteral( "pdfLosslessImages" ), losslessImages ? 1 : 0 );
}

settings.forceVectorOutput = forceVector;
@@ -4446,6 +4451,11 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
else
settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagDisableTiledRasterLayerRenders;

if ( losslessImages )
settings.flags = settings.flags | QgsLayoutRenderContext::FlagLosslessImageRendering;
else
settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagLosslessImageRendering;

return true;
}

@@ -675,6 +675,8 @@ QJsonObject QgsSymbolLegendNode::exportSymbolToJson( const QgsLegendSettings &se
ctx.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * ctx.scaleFactor() ) ) );
ctx.setForceVectorOutput( true );
ctx.setFlag( QgsRenderContext::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
ctx.setFlag( QgsRenderContext::LosslessImageRendering, context.flags() & QgsRenderContext::LosslessImageRendering );

Q_NOWARN_DEPRECATED_POP

// ensure that a minimal expression context is available
@@ -1333,6 +1333,10 @@ void QgsLayoutItem::preparePainter( QPainter *painter )
}

painter->setRenderHint( QPainter::Antialiasing, shouldDrawAntialiased() );

#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
painter->setRenderHint( QPainter::LosslessImageRendering, mLayout && mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagLosslessImageRendering ) );
#endif
}

bool QgsLayoutItem::shouldDrawAntialiased() const
@@ -924,6 +924,11 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
QgsRectangle cExtent = extent();
QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );

#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
if ( mLayout && mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering )
painter->setRenderHint( QPainter::LosslessImageRendering, true );
#endif

if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) ) )
{
// rasterize
@@ -1439,6 +1444,7 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF
// layout-specific overrides of flags
jobMapSettings.setFlag( QgsMapSettings::ForceVectorOutput, true ); // force vector output (no caching of marker images etc.)
jobMapSettings.setFlag( QgsMapSettings::Antialiasing, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
jobMapSettings.setFlag( QgsMapSettings::LosslessImageRendering, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagLosslessImageRendering );
jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
jobMapSettings.setSelectionColor( mLayout->renderContext().selectionColor() );
jobMapSettings.setFlag( QgsMapSettings::DrawSelection, mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagDrawSelection );
@@ -66,6 +66,8 @@ QgsRenderContext::Flags QgsLayoutRenderContext::renderContextFlags() const
flags = flags | QgsRenderContext::Antialiasing;
if ( mFlags & FlagUseAdvancedEffects )
flags = flags | QgsRenderContext::UseAdvancedEffects;
if ( mFlags & FlagLosslessImageRendering )
flags = flags | QgsRenderContext::LosslessImageRendering;

// TODO - expose as layout context flag?
flags |= QgsRenderContext::ForceVectorOutput;
@@ -48,6 +48,7 @@ class CORE_EXPORT QgsLayoutRenderContext : public QObject
FlagDrawSelection = 1 << 7, //!< Draw selection
FlagDisableTiledRasterLayerRenders = 1 << 8, //!< If set, then raster layers will not be drawn as separate tiles. This may improve the appearance in exported files, at the cost of much higher memory usage during exports.
FlagRenderLabelsByMapLayer = 1 << 9, //!< When rendering map items to multi-layered exports, render labels belonging to different layers into separate export layers
FlagLosslessImageRendering = 1 << 10, //!< Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some destination devices (e.g. PDF). This flag only works with builds based on Qt 5.13 or later.
};
Q_DECLARE_FLAGS( Flags, Flag )

@@ -40,6 +40,9 @@ void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter,
painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );

painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
#endif

#ifndef QT_NO_DEBUG
QPaintDevice *paintDevice = painter->device();
@@ -255,6 +255,9 @@ QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&
{
painter = new QPainter( image );
painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
#endif
}
return painter;
}
@@ -312,6 +312,7 @@ class CORE_EXPORT QgsMapSettings : public QgsTemporalRangeObject
RenderPartialOutput = 0x200, //!< Whether to make extra effort to update map image with partially rendered layers (better for interactive map canvas). Added in QGIS 3.0
RenderPreviewJob = 0x400, //!< Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering
RenderBlocking = 0x800, //!< Render and load remote sources in the same thread to ensure rendering remote sources (svg and images). WARNING: this flag must NEVER be used from GUI based applications (like the main QGIS application) or crashes will result. Only for use in external scripts or QGIS server.
LosslessImageRendering = 0x1000, //!< Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some destination devices (e.g. PDF). This flag only works with builds based on Qt 5.13 or later.
// TODO: ignore scale-based visibility (overview)
};
Q_DECLARE_FLAGS( Flags, Flag )
@@ -126,10 +126,15 @@ QgsRenderContext QgsRenderContext::fromQPainter( QPainter *painter )
{
context.setScaleFactor( 3.465 ); //assume 88 dpi as standard value
}

if ( painter && painter->renderHints() & QPainter::Antialiasing )
{
context.setFlag( QgsRenderContext::Antialiasing, true );
}

#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
if ( painter && painter->renderHints() & QPainter::LosslessImageRendering )
context.setFlag( QgsRenderContext::LosslessImageRendering, true );
#endif

return context;
}

@@ -142,6 +147,9 @@ void QgsRenderContext::setPainterFlagsUsingContext( QPainter *painter ) const
return;

painter->setRenderHint( QPainter::Antialiasing, mFlags & QgsRenderContext::Antialiasing );
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
painter->setRenderHint( QPainter::LosslessImageRendering, mFlags & QgsRenderContext::LosslessImageRendering );
#endif
}

QgsCoordinateTransformContext QgsRenderContext::transformContext() const
@@ -205,6 +213,7 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings &mapSet
ctx.setFlag( RenderPartialOutput, mapSettings.testFlag( QgsMapSettings::RenderPartialOutput ) );
ctx.setFlag( RenderPreviewJob, mapSettings.testFlag( QgsMapSettings::RenderPreviewJob ) );
ctx.setFlag( RenderBlocking, mapSettings.testFlag( QgsMapSettings::RenderBlocking ) );
ctx.setFlag( LosslessImageRendering, mapSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
ctx.setScaleFactor( mapSettings.outputDpi() / 25.4 ); // = pixels per mm
ctx.setRendererScale( mapSettings.scale() );
ctx.setExpressionContext( mapSettings.expressionContext() );
@@ -81,6 +81,7 @@ class CORE_EXPORT QgsRenderContext : public QgsTemporalRangeObject
RenderPreviewJob = 0x200, //!< Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering
RenderBlocking = 0x400, //!< Render and load remote sources in the same thread to ensure rendering remote sources (svg and images). WARNING: this flag must NEVER be used from GUI based applications (like the main QGIS application) or crashes will result. Only for use in external scripts or QGIS server.
RenderSymbolPreview = 0x800, //!< The render is for a symbol preview only and map based properties may not be available, so care should be taken to handle map unit based sizes in an appropriate way.
LosslessImageRendering = 0x1000, //!< Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some destination devices (e.g. PDF). This flag only works with builds based on Qt 5.13 or later.
};
Q_DECLARE_FLAGS( Flags, Flag )

@@ -3219,6 +3219,7 @@ void QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext

if ( context.renderContext().flags() & QgsRenderContext::Antialiasing )
pointRenderContext.setFlag( QgsRenderContext::Antialiasing, true );
pointRenderContext.setFlag( QgsRenderContext::LosslessImageRendering, context.renderContext().flags() & QgsRenderContext::LosslessImageRendering );

context.renderContext().setPainterFlagsUsingContext( &p );
QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() );
@@ -59,6 +59,17 @@ QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *paren
mGeoPdfFormatComboBox->addItem( tr( "OGC Best Practice" ) );
}

#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
mComboImageCompression->addItem( tr( "Lossy (JPEG)" ), false );
mComboImageCompression->addItem( tr( "Lossless" ), true );
#else
mComboImageCompression->setDisabled( true );
mComboImageCompression->addItem( tr( "Lossy (JPEG)" ) );
mComboImageCompression->setCurrentIndex( 0 );
mComboImageCompression->setToolTip( tr( "Lossless image compression is available only with QGIS builds using Qt 5.14 or later" ) );
#endif


const QStringList themes = QgsProject::instance()->mapThemeCollection()->mapThemes();
for ( const QString &theme : themes )
{
@@ -170,6 +181,24 @@ bool QgsLayoutPdfExportOptionsDialog::geometriesSimplified() const
return mSimplifyGeometriesCheckbox->isChecked();
}

void QgsLayoutPdfExportOptionsDialog::setLosslessImageExport( bool enabled )
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
mComboImageCompression->setCurrentIndex( mComboImageCompression->findData( enabled ) );
#else
Q_UNUSED( enabled )
#endif
}

bool QgsLayoutPdfExportOptionsDialog::losslessImageExport() const
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
return mComboImageCompression->currentData().toBool();
#else
return false;
#endif
}

void QgsLayoutPdfExportOptionsDialog::setExportGeoPdf( bool enabled )
{
if ( !mGeopdfAvailable )
@@ -85,6 +85,11 @@ class GUI_EXPORT QgsLayoutPdfExportOptionsDialog: public QDialog, private Ui::Qg
//! Returns whether geometry simplification is enabled
bool geometriesSimplified() const;

//! Sets whether to use lossless image compression
void setLosslessImageExport( bool enabled );
//! Returns whether lossless image compression is enabled
bool losslessImageExport() const;

//! Sets whether to export a Geo-PDF
void setExportGeoPdf( bool enabled );
//! Returns whether Geo-PDF export is enabled
@@ -20,6 +20,13 @@
<string>Export Options</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Text export</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="mTextRenderFormatComboBox"/>
</item>
@@ -33,13 +40,6 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Text export</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="mAppendGeoreferenceCheckbox">
<property name="text">
@@ -60,6 +60,16 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Image compression</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="mComboImageCompression"/>
</item>
</layout>
</widget>
</item>
@@ -99,9 +109,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<y>-316</y>
<width>451</width>
<height>612</height>
<height>648</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@@ -300,8 +310,13 @@
<tabstop>mAppendGeoreferenceCheckbox</tabstop>
<tabstop>mIncludeMetadataCheckbox</tabstop>
<tabstop>mTextRenderFormatComboBox</tabstop>
<tabstop>mComboImageCompression</tabstop>
<tabstop>mGeoPDFGroupBox</tabstop>
<tabstop>scrollArea</tabstop>
<tabstop>mGeoPdfFormatComboBox</tabstop>
<tabstop>mIncludeMapThemesCheck</tabstop>
<tabstop>mThemesList</tabstop>
<tabstop>mGeoPdfStructureTree</tabstop>
<tabstop>mDisableRasterTilingCheckBox</tabstop>
<tabstop>mSimplifyGeometriesCheckbox</tabstop>
</tabstops>

0 comments on commit b2cef73

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