QGIS API Documentation  3.14.0-Pi (9f7028fd23)
qgsmaprenderercustompainterjob.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaprenderercustompainterjob.cpp
3  --------------------------------------
4  Date : December 2013
5  Copyright : (C) 2013 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 
18 #include "qgsfeedback.h"
19 #include "qgslabelingengine.h"
20 #include "qgslogger.h"
21 #include "qgsmaplayerrenderer.h"
22 #include "qgsmaplayerlistutils.h"
23 #include "qgsvectorlayerlabeling.h"
24 
25 #include <QtConcurrentRun>
26 
27 //
28 // QgsMapRendererAbstractCustomPainterJob
29 //
30 
32  : QgsMapRendererJob( settings )
33 {
34 
35 }
36 
37 void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
38 {
39  // clear the background
40  painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
41 
42  painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
43 
44 #ifndef QT_NO_DEBUG
45  QPaintDevice *paintDevice = painter->device();
46  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
47  .arg( paintDevice->logicalDpiX() )
49  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() * mSettings.devicePixelRatio(), 1.0 ),
50  "Job::startRender()", errMsg.toLatin1().data() );
51 #endif
52 }
53 
54 
55 //
56 // QgsMapRendererCustomPainterJob
57 //
58 
61  , mPainter( painter )
62  , mActive( false )
63  , mRenderSynchronously( false )
64 {
65  QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
66 }
67 
69 {
70  QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
71  Q_ASSERT( !mFutureWatcher.isRunning() );
72  //cancel();
73 }
74 
76 {
77  if ( isActive() )
78  return;
79 
80  if ( !mPrepareOnly )
81  mRenderingStart.start();
82 
83  mActive = true;
84 
85  mErrors.clear();
86 
87  QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
88 
89  QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
90  QElapsedTimer prepareTime;
91  prepareTime.start();
92 
94 
95  mLabelingEngineV2.reset();
96 
98  {
99  mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
100  mLabelingEngineV2->setMapSettings( mSettings );
101  }
102 
103  bool canUseLabelCache = prepareLabelCache();
104  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
105  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
106  mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
107 
108  QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
109 
110  if ( mRenderSynchronously )
111  {
112  if ( !mPrepareOnly )
113  {
114  // do the rendering right now!
115  doRender();
116  }
117  return;
118  }
119 
120  // now we are ready to start rendering!
121  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
122 
123  mFuture = QtConcurrent::run( staticRender, this );
124  mFutureWatcher.setFuture( mFuture );
125 }
126 
127 
129 {
130  if ( !isActive() )
131  {
132  QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
133  return;
134  }
135 
136  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
137  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
139 
140  QElapsedTimer t;
141  t.start();
142 
143  mFutureWatcher.waitForFinished();
144 
145  QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
146 
147  futureFinished();
148 
149  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
150 }
151 
153 {
154  if ( !isActive() )
155  {
156  QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
157  return;
158  }
159 
160  mLabelJob.context.setRenderingStopped( true );
161  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
162  {
163  it->context.setRenderingStopped( true );
164  if ( it->renderer && it->renderer->feedback() )
165  it->renderer->feedback()->cancel();
166  }
167 }
168 
170 {
171  if ( !isActive() )
172  return;
173 
174  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
175 
176  QElapsedTimer t;
177  t.start();
178 
179  mFutureWatcher.waitForFinished();
180 
181  QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
182 
183  futureFinished();
184 }
185 
187 {
188  return mActive;
189 }
190 
192 {
193  return mLabelJob.cached;
194 }
195 
197 {
198  if ( mLabelingEngineV2 )
199  return mLabelingEngineV2->takeResults();
200  else
201  return nullptr;
202 }
203 
204 
205 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
206 {
207  QEventLoop loop;
208  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
209  loop.exec( flags );
210 }
211 
212 
214 {
215  mRenderSynchronously = true;
216  start();
217  futureFinished();
218  mRenderSynchronously = false;
219 }
220 
222 {
223  mRenderSynchronously = true;
224  mPrepareOnly = true;
225  start();
226  mPrepared = true;
227 }
228 
230 {
231  if ( !mPrepared )
232  return;
233 
234  doRender();
235  futureFinished();
236  mRenderSynchronously = false;
237  mPrepareOnly = false;
238  mPrepared = false;
239 }
240 
241 void QgsMapRendererCustomPainterJob::futureFinished()
242 {
243  mActive = false;
244  if ( !mPrepared ) // can't access from other thread
245  mRenderingTime = mRenderingStart.elapsed();
246  QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
247 
248  if ( !mPrepared )
249  logRenderingTime( mLayerJobs, {}, mLabelJob );
250 
251  // final cleanup
252  cleanupJobs( mLayerJobs );
253  cleanupSecondPassJobs( mSecondPassLayerJobs );
254  cleanupLabelJob( mLabelJob );
255 
256  emit finished();
257 }
258 
259 
260 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
261 {
262  try
263  {
264  self->doRender();
265  }
266  catch ( QgsException &e )
267  {
268  Q_UNUSED( e )
269  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
270  }
271  catch ( std::exception &e )
272  {
273  Q_UNUSED( e )
274  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
275  }
276  catch ( ... )
277  {
278  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
279  }
280 }
281 
282 void QgsMapRendererCustomPainterJob::doRender()
283 {
284  bool hasSecondPass = ! mSecondPassLayerJobs.isEmpty();
285  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
286  QElapsedTimer renderTime;
287  renderTime.start();
288 
289  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
290  {
291  LayerRenderJob &job = *it;
292 
293  if ( job.context.renderingStopped() )
294  break;
295 
296  if ( ! hasSecondPass && job.context.useAdvancedEffects() )
297  {
298  // Set the QPainter composition mode so that this layer is rendered using
299  // the desired blending mode
300  mPainter->setCompositionMode( job.blendMode );
301  }
302 
303  if ( !job.cached )
304  {
305  QElapsedTimer layerTime;
306  layerTime.start();
307 
308  if ( job.img )
309  {
310  job.img->fill( 0 );
311  job.imageInitialized = true;
312  }
313 
314  job.renderer->render();
315 
316  job.renderingTime += layerTime.elapsed();
317  }
318 
319  if ( ! hasSecondPass && job.img )
320  {
321  // If we flattened this layer for alternate blend modes, composite it now
322  mPainter->setOpacity( job.opacity );
323  mPainter->drawImage( 0, 0, *job.img );
324  mPainter->setOpacity( 1.0 );
325  }
326 
327  }
328 
329  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
330 
331  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
332  {
333  if ( !mLabelJob.cached )
334  {
335  QElapsedTimer labelTime;
336  labelTime.start();
337 
338  if ( mLabelJob.img )
339  {
340  QPainter painter;
341  mLabelJob.img->fill( 0 );
342  painter.begin( mLabelJob.img );
343  mLabelJob.context.setPainter( &painter );
344  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
345  painter.end();
346  }
347  else
348  {
349  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
350  }
351 
352  mLabelJob.complete = true;
353  mLabelJob.renderingTime = labelTime.elapsed();
354  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
355  }
356  }
357 
358  if ( ! hasSecondPass )
359  {
360  if ( mLabelJob.img && mLabelJob.complete )
361  {
362  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
363  mPainter->setOpacity( 1.0 );
364  mPainter->drawImage( 0, 0, *mLabelJob.img );
365  }
366  }
367  else
368  {
369  for ( LayerRenderJob &job : mSecondPassLayerJobs )
370  {
371  if ( job.context.renderingStopped() )
372  break;
373 
374  if ( !job.cached )
375  {
376  QElapsedTimer layerTime;
377  layerTime.start();
378 
379  if ( job.img )
380  {
381  job.img->fill( 0 );
382  job.imageInitialized = true;
383  }
384 
385  job.renderer->render();
386 
387  job.renderingTime += layerTime.elapsed();
388  }
389  }
390 
391  composeSecondPass( mSecondPassLayerJobs, mLabelJob );
392 
393  QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
394 
395  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
396  mPainter->setOpacity( 1.0 );
397  mPainter->drawImage( 0, 0, finalImage );
398  }
399 
400  QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
401 }
402 
403 
QgsException
Definition: qgsexception.h:34
QgsMapRendererJob::logRenderingTime
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
QgsMapRendererJob::cleanupJobs
void cleanupJobs(LayerRenderJobs &jobs)
QgsMapRendererCustomPainterJob::waitForFinished
void waitForFinished() override
Block until the job has finished.
Definition: qgsmaprenderercustompainterjob.cpp:169
QgsMapRendererJob::mErrors
Errors mErrors
Definition: qgsmaprendererjob.h:317
QgsMapRendererAbstractCustomPainterJob
Definition: qgsmaprenderercustompainterjob.h:31
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsMapSettings::devicePixelRatio
float devicePixelRatio() const
Returns device pixel ratio Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" di...
Definition: qgsmapsettings.cpp:246
QgsMapRendererCustomPainterJob::isActive
bool isActive() const override
Tell whether the rendering job is currently running in background.
Definition: qgsmaprenderercustompainterjob.cpp:186
QgsLabelingResults
Definition: qgspallabeling.h:1224
QgsMapRendererJob::mRenderingStart
QElapsedTimer mRenderingStart
Definition: qgsmaprendererjob.h:316
qgsmaprenderercustompainterjob.h
QgsMapRendererJob::prepareLabelCache
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
QgsMapRendererJob::prepareJobs
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
QgsDefaultLabelingEngine
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
Definition: qgslabelingengine.h:336
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsMapRendererCustomPainterJob::cancel
void cancel() override
Stop the rendering job - does not return until the job has terminated.
Definition: qgsmaprenderercustompainterjob.cpp:128
QgsMapRendererCustomPainterJob::start
void start() override
Start the rendering job and immediately return.
Definition: qgsmaprenderercustompainterjob.cpp:75
QgsMapRendererJob::cleanupSecondPassJobs
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
QgsMapRendererJob::prepareLabelingJob
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
QgsMapRendererCustomPainterJob::takeLabelingResults
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
Definition: qgsmaprenderercustompainterjob.cpp:196
QgsMapSettings::DrawLabeling
@ DrawLabeling
Enable drawing of labels on top of the map.
Definition: qgsmapsettings.h:306
QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop
void waitForFinishedWithEventLoop(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Wait for the job to be finished - and keep the thread's event loop running while waiting.
Definition: qgsmaprenderercustompainterjob.cpp:205
QgsMapRendererJob::mRenderingTime
int mRenderingTime
Definition: qgsmaprendererjob.h:321
QgsMapRendererCustomPainterJob::cancelWithoutBlocking
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
Definition: qgsmaprenderercustompainterjob.cpp:152
QgsMapRendererCustomPainterJob::usedCachedLabels
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
Definition: qgsmaprenderercustompainterjob.cpp:191
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsException::what
QString what() const
Definition: qgsexception.h:48
QgsMapSettings::Antialiasing
@ Antialiasing
Enable anti-aliasing for map rendering.
Definition: qgsmapsettings.h:302
QgsMapRendererJob::composeImage
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
QgsMapSettings::backgroundColor
QColor backgroundColor() const
Gets the background color of the map.
Definition: qgsmapsettings.h:292
QgsMapRendererCustomPainterJob::QgsMapRendererCustomPainterJob
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
Definition: qgsmaprenderercustompainterjob.cpp:59
qgsmaplayerrenderer.h
QgsMapRendererJob::cleanupLabelJob
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
QgsMapRendererCustomPainterJob::prepare
void prepare()
Prepares the job for rendering synchronously in a background thread.
Definition: qgsmaprenderercustompainterjob.cpp:221
QgsMapRendererCustomPainterJob
Definition: qgsmaprenderercustompainterjob.h:63
QgsMapRendererCustomPainterJob::renderSynchronously
void renderSynchronously()
Render the map synchronously in this thread.
Definition: qgsmaprenderercustompainterjob.cpp:213
QgsMapSettings::deviceOutputSize
QSize deviceOutputSize() const
Returns the device output size of the map canvas This is equivalent to the output size multiplicated ...
Definition: qgsmapsettings.cpp:257
QgsMapRendererAbstractCustomPainterJob::preparePainter
void preparePainter(QPainter *painter, const QColor &backgroundColor=Qt::transparent)
Prepares the given painter ready for a map render.
Definition: qgsmaprenderercustompainterjob.cpp:37
QgsMapRendererCustomPainterJob::~QgsMapRendererCustomPainterJob
~QgsMapRendererCustomPainterJob() override
Definition: qgsmaprenderercustompainterjob.cpp:68
QgsMapSettings::testFlag
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
Definition: qgsmapsettings.cpp:354
QgsMapRendererJob::mSettings
QgsMapSettings mSettings
Definition: qgsmaprendererjob.h:315
QgsMapRendererJob::composeSecondPass
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
QgsMapRendererJob
Definition: qgsmaprendererjob.h:186
QgsMapRendererCustomPainterJob::renderPrepared
void renderPrepared()
Render a pre-prepared job.
Definition: qgsmaprenderercustompainterjob.cpp:229
qgsvectorlayerlabeling.h
QgsMapRendererAbstractCustomPainterJob::QgsMapRendererAbstractCustomPainterJob
QgsMapRendererAbstractCustomPainterJob(const QgsMapSettings &settings)
Constructor for QgsMapRendererAbstractCustomPainterJob, using the given map settings.
Definition: qgsmaprenderercustompainterjob.cpp:31
qgsmaplayerlistutils.h
qgslogger.h
QgsMapSettings::outputDpi
double outputDpi() const
Returns DPI used for conversion between real world units (e.g.
Definition: qgsmapsettings.cpp:262
QgsMapRendererJob::prepareSecondPassJobs
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
QgsMapSettings
Definition: qgsmapsettings.h:86
qgsfeedback.h
qgslabelingengine.h
QgsMapRendererJob::finished
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapRendererJob::drawLabeling
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)