Skip to content

Commit 0f5b3fd

Browse files
authoredFeb 7, 2017
Merge pull request #4110 from nyalldawson/label_cache
[FEATURE] Cache labeling result to avoid unnecessary redraws when refreshing canvas
2 parents 61523c8 + a08137f commit 0f5b3fd

19 files changed

+770
-44
lines changed
 

‎python/core/qgsmaprenderercustompainterjob.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class QgsMapRendererCustomPainterJob : QgsMapRendererJob
2121
virtual void cancel();
2222
virtual void waitForFinished();
2323
virtual bool isActive() const;
24+
virtual bool usedCachedLabels() const;
2425
virtual QgsLabelingResults* takeLabelingResults() /Transfer/;
2526

2627
//! @note not available in python bindings

‎python/core/qgsmaprendererjob.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class QgsMapRendererJob : QObject
2424
//! Tell whether the rendering job is currently running in background.
2525
virtual bool isActive() const = 0;
2626

27+
virtual bool usedCachedLabels() const = 0;
28+
2729
//! Get pointer to internal labeling engine (in order to get access to the results)
2830
virtual QgsLabelingResults* takeLabelingResults() = 0 /Transfer/;
2931

‎python/core/qgsmaprendererparalleljob.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class QgsMapRendererParallelJob : QgsMapRendererQImageJob
2020
virtual void cancel();
2121
virtual void waitForFinished();
2222
virtual bool isActive() const;
23+
virtual bool usedCachedLabels() const;
2324

2425
virtual QgsLabelingResults* takeLabelingResults() /Transfer/;
2526

‎python/core/qgsmaprenderersequentialjob.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class QgsMapRendererSequentialJob : QgsMapRendererQImageJob
2121
virtual void cancel();
2222
virtual void waitForFinished();
2323
virtual bool isActive() const;
24+
virtual bool usedCachedLabels() const;
2425

2526
virtual QgsLabelingResults* takeLabelingResults() /Transfer/;
2627

‎src/core/qgsmaprenderercustompainterjob.cpp

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "qgspallabeling.h"
2424
#include "qgsvectorlayer.h"
2525
#include "qgsrenderer.h"
26+
#include "qgsmaplayerlistutils.h"
2627

2728
QgsMapRendererCustomPainterJob::QgsMapRendererCustomPainterJob( const QgsMapSettings& settings, QPainter* painter )
2829
: QgsMapRendererJob( settings )
@@ -82,7 +83,9 @@ void QgsMapRendererCustomPainterJob::start()
8283
mLabelingEngineV2->setMapSettings( mSettings );
8384
}
8485

86+
bool canUseLabelCache = prepareLabelCache();
8587
mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2 );
88+
mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2, canUseLabelCache );
8689

8790
QgsDebugMsg( "Rendering prepared in (seconds): " + QString( "%1" ).arg( prepareTime.elapsed() / 1000.0 ) );
8891

@@ -112,7 +115,7 @@ void QgsMapRendererCustomPainterJob::cancel()
112115
QgsDebugMsg( "QPAINTER canceling" );
113116
disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
114117

115-
mLabelingRenderContext.setRenderingStopped( true );
118+
mLabelJob.context.setRenderingStopped( true );
116119
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
117120
{
118121
it->context.setRenderingStopped( true );
@@ -154,6 +157,10 @@ bool QgsMapRendererCustomPainterJob::isActive() const
154157
return mActive;
155158
}
156159

160+
bool QgsMapRendererCustomPainterJob::usedCachedLabels() const
161+
{
162+
return mLabelJob.cached;
163+
}
157164

158165
QgsLabelingResults* QgsMapRendererCustomPainterJob::takeLabelingResults()
159166
{
@@ -187,10 +194,11 @@ void QgsMapRendererCustomPainterJob::futureFinished()
187194
mRenderingTime = mRenderingStart.elapsed();
188195
QgsDebugMsg( "QPAINTER futureFinished" );
189196

190-
logRenderingTime( mLayerJobs );
197+
logRenderingTime( mLayerJobs, mLabelJob );
191198

192199
// final cleanup
193200
cleanupJobs( mLayerJobs );
201+
cleanupLabelJob( mLabelJob );
194202

195203
emit finished();
196204
}
@@ -263,8 +271,38 @@ void QgsMapRendererCustomPainterJob::doRender()
263271

264272
QgsDebugMsg( "Done rendering map layers" );
265273

266-
if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelingRenderContext.renderingStopped() )
267-
drawLabeling( mSettings, mLabelingRenderContext, mLabelingEngineV2, mPainter );
274+
if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
275+
{
276+
if ( !mLabelJob.cached )
277+
{
278+
QTime labelTime;
279+
labelTime.start();
280+
281+
if ( mLabelJob.img )
282+
{
283+
QPainter painter;
284+
mLabelJob.img->fill( 0 );
285+
painter.begin( mLabelJob.img );
286+
mLabelJob.context.setPainter( &painter );
287+
drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2, &painter );
288+
painter.end();
289+
}
290+
else
291+
{
292+
drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2, mPainter );
293+
}
294+
295+
mLabelJob.complete = true;
296+
mLabelJob.renderingTime = labelTime.elapsed();
297+
mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
298+
}
299+
}
300+
if ( mLabelJob.img && mLabelJob.complete )
301+
{
302+
mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
303+
mPainter->setOpacity( 1.0 );
304+
mPainter->drawImage( 0, 0, *mLabelJob.img );
305+
}
268306

269307
QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
270308
}

‎src/core/qgsmaprenderercustompainterjob.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class CORE_EXPORT QgsMapRendererCustomPainterJob : public QgsMapRendererJob
4141
virtual void cancel() override;
4242
virtual void waitForFinished() override;
4343
virtual bool isActive() const override;
44+
virtual bool usedCachedLabels() const override;
4445
virtual QgsLabelingResults* takeLabelingResults() override;
4546

4647
//! @note not available in python bindings
@@ -83,11 +84,11 @@ class CORE_EXPORT QgsMapRendererCustomPainterJob : public QgsMapRendererJob
8384
QPainter* mPainter;
8485
QFuture<void> mFuture;
8586
QFutureWatcher<void> mFutureWatcher;
86-
QgsRenderContext mLabelingRenderContext;
8787
QgsLabelingEngine* mLabelingEngineV2;
8888

8989
bool mActive;
9090
LayerRenderJobs mLayerJobs;
91+
LabelRenderJob mLabelJob;
9192
bool mRenderSynchronously;
9293

9394
};

‎src/core/qgsmaprendererjob.cpp

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,14 @@
3333
#include "qgsvectorlayerrenderer.h"
3434
#include "qgsvectorlayer.h"
3535
#include "qgscsexception.h"
36+
#include "qgslabelingengine.h"
37+
#include "qgsmaplayerlistutils.h"
38+
#include "qgsvectorlayerlabeling.h"
3639

3740
///@cond PRIVATE
3841

42+
const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
43+
3944
QgsMapRendererJob::QgsMapRendererJob( const QgsMapSettings& settings )
4045
: mSettings( settings )
4146
, mCache( nullptr )
@@ -66,6 +71,39 @@ const QgsMapSettings& QgsMapRendererJob::mapSettings() const
6671
return mSettings;
6772
}
6873

74+
bool QgsMapRendererJob::prepareLabelCache() const
75+
{
76+
bool canCache = mCache;
77+
78+
// calculate which layers will be labeled
79+
QSet< QgsMapLayer* > labeledLayers;
80+
Q_FOREACH ( const QgsMapLayer* ml, mSettings.layers() )
81+
{
82+
QgsVectorLayer* vl = const_cast< QgsVectorLayer* >( qobject_cast<const QgsVectorLayer *>( ml ) );
83+
if ( vl && QgsPalLabeling::staticWillUseLayer( vl ) )
84+
labeledLayers << vl;
85+
if ( vl && vl->labeling() && vl->labeling()->requiresAdvancedEffects( vl ) )
86+
{
87+
canCache = false;
88+
break;
89+
}
90+
}
91+
92+
if ( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) )
93+
{
94+
// we may need to clear label cache and re-register labeled features - check for that here
95+
96+
// can we reuse the cached label solution?
97+
bool canUseCache = canCache && mCache->dependentLayers( LABEL_CACHE_ID ).toSet() == labeledLayers;
98+
if ( !canUseCache )
99+
{
100+
// no - participating layers have changed
101+
mCache->clearCacheImage( LABEL_CACHE_ID );
102+
}
103+
}
104+
return canCache;
105+
}
106+
69107

70108
bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform& ct, QgsRectangle &extent, QgsRectangle &r2 )
71109
{
@@ -179,13 +217,15 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsLabelingEn
179217
QListIterator<QgsMapLayer*> li( mSettings.layers() );
180218
li.toBack();
181219

220+
bool cacheValid = false;
182221
if ( mCache )
183222
{
184-
bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
223+
cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
185224
QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
186-
Q_UNUSED( cacheValid );
187225
}
188226

227+
bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
228+
189229
mGeometryCaches.clear();
190230

191231
while ( li.hasPrevious() )
@@ -229,8 +269,12 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsLabelingEn
229269
if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
230270
{
231271
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
232-
if ( vl->isEditable() || ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( vl ) ) )
272+
bool requiresLabeling = false;
273+
requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( vl ) ) && requiresLabelRedraw;
274+
if ( vl->isEditable() || requiresLabeling )
275+
{
233276
mCache->clearCacheImage( ml->id() );
277+
}
234278
}
235279

236280
layerJobs.append( LayerRenderJob() );
@@ -312,6 +356,47 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsLabelingEn
312356
return layerJobs;
313357
}
314358

359+
LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter* painter, QgsLabelingEngine* labelingEngine2, bool canUseLabelCache )
360+
{
361+
LabelRenderJob job;
362+
job.context = QgsRenderContext::fromMapSettings( mSettings );
363+
job.context.setPainter( painter );
364+
job.context.setLabelingEngine( labelingEngine2 );
365+
job.context.setExtent( mSettings.visibleExtent() );
366+
367+
// if we can use the cache, let's do it and avoid rendering!
368+
bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
369+
if ( hasCache )
370+
{
371+
job.cached = true;
372+
job.complete = true;
373+
job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
374+
job.context.setPainter( nullptr );
375+
}
376+
else
377+
{
378+
if ( canUseLabelCache && ( mCache || !painter ) )
379+
{
380+
// Flattened image for drawing labels
381+
QImage * mypFlattenedImage = nullptr;
382+
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
383+
mSettings.outputSize().height(),
384+
mSettings.outputImageFormat() );
385+
if ( mypFlattenedImage->isNull() )
386+
{
387+
mErrors.append( Error( QStringLiteral( "labels" ), tr( "Insufficient memory for label image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
388+
delete mypFlattenedImage;
389+
}
390+
else
391+
{
392+
job.img = mypFlattenedImage;
393+
}
394+
}
395+
}
396+
397+
return job;
398+
}
399+
315400

316401
void QgsMapRendererJob::cleanupJobs( LayerRenderJobs& jobs )
317402
{
@@ -343,13 +428,29 @@ void QgsMapRendererJob::cleanupJobs( LayerRenderJobs& jobs )
343428
}
344429
}
345430

431+
346432
jobs.clear();
347433

348434
updateLayerGeometryCaches();
349435
}
350436

437+
void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob& job )
438+
{
439+
if ( job.img )
440+
{
441+
if ( mCache && !job.cached && !job.context.renderingStopped() )
442+
{
443+
QgsDebugMsg( "caching label result image" );
444+
mCache->setCacheImage( LABEL_CACHE_ID, *job.img, _qgis_listQPointerToRaw( job.participatingLayers ) );
445+
}
351446

352-
QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
447+
delete job.img;
448+
job.img = nullptr;
449+
}
450+
}
451+
452+
453+
QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs, const LabelRenderJob& labelJob )
353454
{
354455
QImage image( settings.outputSize(), settings.outputImageFormat() );
355456
image.fill( settings.backgroundColor().rgba() );
@@ -368,11 +469,21 @@ QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const La
368469
painter.drawImage( 0, 0, *job.img );
369470
}
370471

472+
// IMPORTANT - don't draw labelJob img before the label job is complete,
473+
// as the image is uninitialized and full of garbage before the label job
474+
// commences
475+
if ( labelJob.img && labelJob.complete )
476+
{
477+
painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
478+
painter.setOpacity( 1.0 );
479+
painter.drawImage( 0, 0, *labelJob.img );
480+
}
481+
371482
painter.end();
372483
return image;
373484
}
374485

375-
void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs& jobs )
486+
void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs& jobs, const LabelRenderJob& labelJob )
376487
{
377488
QSettings settings;
378489
if ( !settings.value( QStringLiteral( "/Map/logCanvasRefreshEvent" ), false ).toBool() )
@@ -382,6 +493,8 @@ void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs& jobs )
382493
Q_FOREACH ( const LayerRenderJob& job, jobs )
383494
elapsed.insert( job.renderingTime, job.layer ? job.layer->id() : QString() );
384495

496+
elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
497+
385498
QList<int> tt( elapsed.uniqueKeys() );
386499
std::sort( tt.begin(), tt.end(), std::greater<int>() );
387500
Q_FOREACH ( int t, tt )

‎src/core/qgsmaprendererjob.h

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,30 @@ struct LayerRenderJob
5656

5757
typedef QList<LayerRenderJob> LayerRenderJobs;
5858

59+
/** \ingroup core
60+
* Structure keeping low-level label rendering job information.
61+
*/
62+
struct LabelRenderJob
63+
{
64+
QgsRenderContext context;
65+
66+
/**
67+
* May be null if it is not necessary to draw to separate image (e.g. using composition modes which prevent "flattening" the layer).
68+
* Note that if complete is false then img will be uninitialized and contain random data!.
69+
*/
70+
QImage* img = nullptr;
71+
//! If true, img already contains cached image from previous rendering
72+
bool cached = false;
73+
//! Will be true if labeling is eligible for caching
74+
bool canUseCache = false;
75+
//! If true then label render is complete
76+
bool complete = false;
77+
//! Time it took to render the labels in ms (it is -1 if not rendered or still rendering)
78+
int renderingTime = -1;
79+
//! List of layers which participated in the labeling solution
80+
QList< QPointer< QgsMapLayer > > participatingLayers;
81+
};
82+
5983
///@endcond PRIVATE
6084

6185
/** \ingroup core
@@ -103,7 +127,20 @@ class CORE_EXPORT QgsMapRendererJob : public QObject
103127
//! Tell whether the rendering job is currently running in background.
104128
virtual bool isActive() const = 0;
105129

106-
//! Get pointer to internal labeling engine (in order to get access to the results)
130+
/**
131+
* Returns true if the render job was able to use a cached labeling solution.
132+
* If so, any previously stored labeling results (see takeLabelingResults())
133+
* should be retained.
134+
* @see takeLabelingResults()
135+
* @note added in QGIS 3.0
136+
*/
137+
virtual bool usedCachedLabels() const = 0;
138+
139+
/**
140+
* Get pointer to internal labeling engine (in order to get access to the results).
141+
* This should not be used if cached labeling was redrawn - see usedCachedLabels().
142+
* @see usedCachedLabels()
143+
*/
107144
virtual QgsLabelingResults* takeLabelingResults() = 0;
108145

109146
//! @note Added in QGIS 3.0
@@ -153,6 +190,12 @@ class CORE_EXPORT QgsMapRendererJob : public QObject
153190
*/
154191
const QgsMapSettings& mapSettings() const;
155192

193+
/**
194+
* QgsMapRendererCache ID string for cached label image.
195+
* @note not available in Python bindings
196+
*/
197+
static const QString LABEL_CACHE_ID;
198+
156199
signals:
157200

158201
/**
@@ -177,18 +220,40 @@ class CORE_EXPORT QgsMapRendererJob : public QObject
177220

178221
int mRenderingTime = 0;
179222

223+
/**
224+
* Prepares the cache for storing the result of labeling. Returns false if
225+
* the render cannot use cached labels and should not cache the result.
226+
* @note not available in Python bindings
227+
*/
228+
bool prepareLabelCache() const;
229+
180230
//! @note not available in python bindings
181231
LayerRenderJobs prepareJobs( QPainter* painter, QgsLabelingEngine* labelingEngine2 );
182232

233+
/**
234+
* Prepares a labeling job.
235+
* @note not available in python bindings
236+
* @note added in QGIS 3.0
237+
*/
238+
LabelRenderJob prepareLabelingJob( QPainter* painter, QgsLabelingEngine* labelingEngine2, bool canUseLabelCache = true );
239+
183240
//! @note not available in python bindings
184-
static QImage composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs );
241+
static QImage composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs, const LabelRenderJob& labelJob );
185242

186243
//! @note not available in python bindings
187-
void logRenderingTime( const LayerRenderJobs& jobs );
244+
void logRenderingTime( const LayerRenderJobs& jobs, const LabelRenderJob& labelJob );
188245

189246
//! @note not available in python bindings
190247
void cleanupJobs( LayerRenderJobs& jobs );
191248

249+
/**
250+
* Handles clean up tasks for a label job, including deletion of images and storing cached
251+
* label results.
252+
* @note added in QGIS 3.0
253+
* @note not available in python bindings
254+
*/
255+
void cleanupLabelJob( LabelRenderJob& job );
256+
192257
//! @note not available in Python bindings
193258
static void drawLabeling( const QgsMapSettings& settings, QgsRenderContext& renderContext, QgsLabelingEngine* labelingEngine2, QPainter* painter );
194259

‎src/core/qgsmaprendererparalleljob.cpp

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "qgspallabeling.h"
2323
#include "qgsproject.h"
2424
#include "qgsmaplayer.h"
25+
#include "qgsmaplayerlistutils.h"
2526

2627
#include <QtConcurrentMap>
2728

@@ -62,7 +63,9 @@ void QgsMapRendererParallelJob::start()
6263
mLabelingEngineV2->setMapSettings( mSettings );
6364
}
6465

66+
bool canUseLabelCache = prepareLabelCache();
6567
mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2 );
68+
mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2, canUseLabelCache );
6669

6770
QgsDebugMsg( QString( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ) );
6871

@@ -81,7 +84,7 @@ void QgsMapRendererParallelJob::cancel()
8184

8285
QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );
8386

84-
mLabelingRenderContext.setRenderingStopped( true );
87+
mLabelJob.context.setRenderingStopped( true );
8588
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
8689
{
8790
it->context.setRenderingStopped( true );
@@ -151,6 +154,11 @@ bool QgsMapRendererParallelJob::isActive() const
151154
return mStatus != Idle;
152155
}
153156

157+
bool QgsMapRendererParallelJob::usedCachedLabels() const
158+
{
159+
return mLabelJob.cached;
160+
}
161+
154162
QgsLabelingResults* QgsMapRendererParallelJob::takeLabelingResults()
155163
{
156164
if ( mLabelingEngineV2 )
@@ -162,7 +170,7 @@ QgsLabelingResults* QgsMapRendererParallelJob::takeLabelingResults()
162170
QImage QgsMapRendererParallelJob::renderedImage()
163171
{
164172
if ( mStatus == RenderingLayers )
165-
return composeImage( mSettings, mLayerJobs );
173+
return composeImage( mSettings, mLayerJobs, mLabelJob );
166174
else
167175
return mFinalImage; // when rendering labels or idle
168176
}
@@ -172,15 +180,11 @@ void QgsMapRendererParallelJob::renderLayersFinished()
172180
Q_ASSERT( mStatus == RenderingLayers );
173181

174182
// compose final image
175-
mFinalImage = composeImage( mSettings, mLayerJobs );
176-
177-
logRenderingTime( mLayerJobs );
178-
179-
cleanupJobs( mLayerJobs );
183+
mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
180184

181185
QgsDebugMsg( "PARALLEL layers finished" );
182186

183-
if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelingRenderContext.renderingStopped() )
187+
if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
184188
{
185189
mStatus = RenderingLabels;
186190

@@ -201,6 +205,12 @@ void QgsMapRendererParallelJob::renderingFinished()
201205
{
202206
QgsDebugMsg( "PARALLEL finished" );
203207

208+
logRenderingTime( mLayerJobs, mLabelJob );
209+
210+
cleanupJobs( mLayerJobs );
211+
212+
cleanupLabelJob( mLabelJob );
213+
204214
mStatus = Idle;
205215

206216
mRenderingTime = mRenderingStart.elapsed();
@@ -249,27 +259,53 @@ void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob& job )
249259

250260
void QgsMapRendererParallelJob::renderLabelsStatic( QgsMapRendererParallelJob* self )
251261
{
252-
QPainter painter( &self->mFinalImage );
262+
LabelRenderJob& job = self->mLabelJob;
253263

254-
try
255-
{
256-
drawLabeling( self->mSettings, self->mLabelingRenderContext, self->mLabelingEngineV2, &painter );
257-
}
258-
catch ( QgsException & e )
264+
if ( !job.cached )
259265
{
260-
Q_UNUSED( e );
261-
QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
266+
QTime labelTime;
267+
labelTime.start();
268+
269+
QPainter painter;
270+
if ( job.img )
271+
{
272+
job.img->fill( 0 );
273+
painter.begin( job.img );
274+
}
275+
else
276+
{
277+
painter.begin( &self->mFinalImage );
278+
}
279+
280+
// draw the labels!
281+
try
282+
{
283+
drawLabeling( self->mSettings, job.context, self->mLabelingEngineV2, &painter );
284+
}
285+
catch ( QgsException & e )
286+
{
287+
Q_UNUSED( e );
288+
QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
289+
}
290+
catch ( std::exception & e )
291+
{
292+
Q_UNUSED( e );
293+
QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
294+
}
295+
catch ( ... )
296+
{
297+
QgsDebugMsg( "Caught unhandled unknown exception" );
298+
}
299+
300+
painter.end();
301+
302+
job.renderingTime = labelTime.elapsed();
303+
job.complete = true;
304+
job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
305+
if ( job.img )
306+
{
307+
self->mFinalImage = composeImage( self->mSettings, self->mLayerJobs, self->mLabelJob );
308+
}
262309
}
263-
catch ( std::exception & e )
264-
{
265-
Q_UNUSED( e );
266-
QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
267-
}
268-
catch ( ... )
269-
{
270-
QgsDebugMsg( "Caught unhandled unknown exception" );
271-
}
272-
273-
painter.end();
274310
}
275311

‎src/core/qgsmaprendererparalleljob.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class CORE_EXPORT QgsMapRendererParallelJob : public QgsMapRendererQImageJob
3939
virtual void waitForFinished() override;
4040
virtual bool isActive() const override;
4141

42+
virtual bool usedCachedLabels() const override;
4243
virtual QgsLabelingResults* takeLabelingResults() override;
4344

4445
// from QgsMapRendererJobWithPreview
@@ -52,7 +53,9 @@ class CORE_EXPORT QgsMapRendererParallelJob : public QgsMapRendererQImageJob
5253

5354
private:
5455

56+
//! @note not available in Python bindings
5557
static void renderLayerStatic( LayerRenderJob& job );
58+
//! @note not available in Python bindings
5659
static void renderLabelsStatic( QgsMapRendererParallelJob* self );
5760

5861
QImage mFinalImage;
@@ -64,10 +67,10 @@ class CORE_EXPORT QgsMapRendererParallelJob : public QgsMapRendererQImageJob
6467
QFutureWatcher<void> mFutureWatcher;
6568

6669
LayerRenderJobs mLayerJobs;
70+
LabelRenderJob mLabelJob;
6771

6872
//! New labeling engine
6973
QgsLabelingEngine* mLabelingEngineV2;
70-
QgsRenderContext mLabelingRenderContext;
7174
QFuture<void> mLabelingFuture;
7275
QFutureWatcher<void> mLabelingFutureWatcher;
7376

‎src/core/qgsmaprenderersequentialjob.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ bool QgsMapRendererSequentialJob::isActive() const
9898
return nullptr != mInternalJob;
9999
}
100100

101+
bool QgsMapRendererSequentialJob::usedCachedLabels() const
102+
{
103+
return mUsedCachedLabels;
104+
}
105+
101106
QgsLabelingResults* QgsMapRendererSequentialJob::takeLabelingResults()
102107
{
103108
QgsLabelingResults* tmp = mLabelingResults;
@@ -110,7 +115,7 @@ QImage QgsMapRendererSequentialJob::renderedImage()
110115
{
111116
if ( isActive() && mCache )
112117
// this will allow immediate display of cached layers and at the same time updates of the layer being rendered
113-
return composeImage( mSettings, mInternalJob->jobs() );
118+
return composeImage( mSettings, mInternalJob->jobs(), LabelRenderJob() );
114119
else
115120
return mImage;
116121
}
@@ -125,6 +130,7 @@ void QgsMapRendererSequentialJob::internalFinished()
125130
mPainter = nullptr;
126131

127132
mLabelingResults = mInternalJob->takeLabelingResults();
133+
mUsedCachedLabels = mInternalJob->usedCachedLabels();
128134

129135
mErrors = mInternalJob->errors();
130136

‎src/core/qgsmaprenderersequentialjob.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class CORE_EXPORT QgsMapRendererSequentialJob : public QgsMapRendererQImageJob
4141
virtual void waitForFinished() override;
4242
virtual bool isActive() const override;
4343

44+
virtual bool usedCachedLabels() const override;
4445
virtual QgsLabelingResults* takeLabelingResults() override;
4546

4647
// from QgsMapRendererJobWithPreview
@@ -56,6 +57,7 @@ class CORE_EXPORT QgsMapRendererSequentialJob : public QgsMapRendererQImageJob
5657
QImage mImage;
5758
QPainter* mPainter;
5859
QgsLabelingResults* mLabelingResults;
60+
bool mUsedCachedLabels = false;
5961

6062
};
6163

‎src/core/qgsrulebasedlabeling.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ void QgsRuleBasedLabeling::Rule::updateElseRules()
119119
}
120120
}
121121

122+
bool QgsRuleBasedLabeling::Rule::requiresAdvancedEffects() const
123+
{
124+
if ( mSettings && mSettings->format().containsAdvancedEffects() )
125+
return true;
126+
127+
Q_FOREACH ( Rule* rule, mChildren )
128+
{
129+
if ( rule->requiresAdvancedEffects() )
130+
return true;
131+
}
132+
133+
return false;
134+
}
135+
122136
void QgsRuleBasedLabeling::Rule::subProviderIds( QStringList& list ) const
123137
{
124138
Q_FOREACH ( const Rule* rule, mChildren )
@@ -437,3 +451,8 @@ QgsPalLayerSettings QgsRuleBasedLabeling::settings( QgsVectorLayer* layer, const
437451

438452
return QgsPalLayerSettings();
439453
}
454+
455+
bool QgsRuleBasedLabeling::requiresAdvancedEffects( QgsVectorLayer* ) const
456+
{
457+
return mRootRule->requiresAdvancedEffects();
458+
}

‎src/core/qgsrulebasedlabeling.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
260260
//! register individual features
261261
RegisterResult registerFeature( QgsFeature& feature, QgsRenderContext& context, RuleToProviderMap& subProviders, QgsGeometry* obstacleGeometry = nullptr );
262262

263+
/**
264+
* Returns true if this rule or any of its children requires advanced composition effects
265+
* to render.
266+
*/
267+
bool requiresAdvancedEffects() const;
268+
263269
protected:
264270

265271
/**
@@ -326,6 +332,7 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
326332
virtual QgsVectorLayerLabelProvider *provider( QgsVectorLayer* layer ) const override;
327333
virtual QStringList subProviders() const override;
328334
virtual QgsPalLayerSettings settings( QgsVectorLayer* layer, const QString& providerId = QString() ) const override;
335+
bool requiresAdvancedEffects( QgsVectorLayer* layer ) const override;
329336

330337
protected:
331338
Rule* mRootRule;

‎src/core/qgsvectorlayerlabeling.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,8 @@ QgsPalLayerSettings QgsVectorLayerSimpleLabeling::settings( QgsVectorLayer* laye
6060
else
6161
return QgsPalLayerSettings();
6262
}
63+
64+
bool QgsVectorLayerSimpleLabeling::requiresAdvancedEffects( QgsVectorLayer* layer ) const
65+
{
66+
return settings( layer ).format().containsAdvancedEffects();
67+
}

‎src/core/qgsvectorlayerlabeling.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ class CORE_EXPORT QgsAbstractVectorLayerLabeling
5656
//! they are identified by their ID (e.g. in case of rule-based labeling, provider ID == rule key)
5757
virtual QgsPalLayerSettings settings( QgsVectorLayer* layer, const QString& providerId = QString() ) const = 0;
5858

59+
/**
60+
* Returns true if drawing labels requires advanced effects like composition
61+
* modes, which could prevent it being used as an isolated cached image
62+
* or exported to a vector format.
63+
* @note added in QGIS 3.0
64+
*/
65+
virtual bool requiresAdvancedEffects( QgsVectorLayer* layer ) const = 0;
66+
5967
// static stuff
6068

6169
//! Try to create instance of an implementation based on the XML data
@@ -79,6 +87,7 @@ class CORE_EXPORT QgsVectorLayerSimpleLabeling : public QgsAbstractVectorLayerLa
7987
virtual QgsVectorLayerLabelProvider* provider( QgsVectorLayer* layer ) const override;
8088
virtual QDomElement save( QDomDocument& doc ) const override;
8189
virtual QgsPalLayerSettings settings( QgsVectorLayer* layer, const QString& providerId = QString() ) const override;
90+
bool requiresAdvancedEffects( QgsVectorLayer* layer ) const override;
8291
};
8392

8493
#endif // QGSVECTORLAYERLABELING_H

‎src/gui/qgsmapcanvas.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,8 +546,11 @@ void QgsMapCanvas::rendererJobFinished()
546546
{
547547
// take labeling results before emitting renderComplete, so labeling map tools
548548
// connected to signal work with correct results
549-
delete mLabelingResults;
550-
mLabelingResults = mJob->takeLabelingResults();
549+
if ( !mJob->usedCachedLabels() )
550+
{
551+
delete mLabelingResults;
552+
mLabelingResults = mJob->takeLabelingResults();
553+
}
551554

552555
QImage img = mJob->renderedImage();
553556

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py)
6565
ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py)
6666
ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py)
6767
ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py)
68+
ADD_PYTHON_TEST(PyQgsMapRenderer test_qgsmaprenderer.py)
6869
ADD_PYTHON_TEST(PyQgsMapRendererCache test_qgsmaprenderercache.py)
6970
ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py)
7071
ADD_PYTHON_TEST(PyQgsMargins test_qgsmargins.py)

‎tests/src/python/test_qgsmaprenderer.py

Lines changed: 413 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.