QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
44  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
45 #endif
46 
47 #ifndef QT_NO_DEBUG
48  QPaintDevice *paintDevice = painter->device();
49  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
50  .arg( paintDevice->logicalDpiX() )
52  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() * mSettings.devicePixelRatio(), 1.0 ),
53  "Job::startRender()", errMsg.toLatin1().data() );
54 #endif
55 }
56 
57 
58 //
59 // QgsMapRendererCustomPainterJob
60 //
61 
64  , mPainter( painter )
65  , mActive( false )
66  , mRenderSynchronously( false )
67 {
68  QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
69 }
70 
72 {
73  QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
74  Q_ASSERT( !mFutureWatcher.isRunning() );
75  //cancel();
76 }
77 
79 {
80  if ( isActive() )
81  return;
82 
83  if ( !mPrepareOnly )
84  mRenderingStart.start();
85 
86  mActive = true;
87 
88  mErrors.clear();
89 
90  QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
91 
92  QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
93  QElapsedTimer prepareTime;
94  prepareTime.start();
95 
97 
98  mLabelingEngineV2.reset();
99 
101  {
102  mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
103  mLabelingEngineV2->setMapSettings( mSettings );
104  }
105 
106  bool canUseLabelCache = prepareLabelCache();
107  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
108  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
109  mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
110 
111  QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
112 
113  if ( mRenderSynchronously )
114  {
115  if ( !mPrepareOnly )
116  {
117  // do the rendering right now!
118  doRender();
119  }
120  return;
121  }
122 
123  // now we are ready to start rendering!
124  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
125 
126  mFuture = QtConcurrent::run( staticRender, this );
127  mFutureWatcher.setFuture( mFuture );
128 }
129 
130 
132 {
133  if ( !isActive() )
134  {
135  QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
136  return;
137  }
138 
139  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
140  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
142 
143  QElapsedTimer t;
144  t.start();
145 
146  mFutureWatcher.waitForFinished();
147 
148  QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
149 
150  futureFinished();
151 
152  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
153 }
154 
156 {
157  if ( !isActive() )
158  {
159  QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
160  return;
161  }
162 
163  mLabelJob.context.setRenderingStopped( true );
164  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
165  {
166  it->context.setRenderingStopped( true );
167  if ( it->renderer && it->renderer->feedback() )
168  it->renderer->feedback()->cancel();
169  }
170 }
171 
173 {
174  if ( !isActive() )
175  return;
176 
177  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
178 
179  QElapsedTimer t;
180  t.start();
181 
182  mFutureWatcher.waitForFinished();
183 
184  QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
185 
186  futureFinished();
187 }
188 
190 {
191  return mActive;
192 }
193 
195 {
196  return mLabelJob.cached;
197 }
198 
200 {
201  if ( mLabelingEngineV2 )
202  return mLabelingEngineV2->takeResults();
203  else
204  return nullptr;
205 }
206 
207 
208 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
209 {
210  QEventLoop loop;
211  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
212  loop.exec( flags );
213 }
214 
215 
217 {
218  mRenderSynchronously = true;
219  start();
220  futureFinished();
221  mRenderSynchronously = false;
222 }
223 
225 {
226  mRenderSynchronously = true;
227  mPrepareOnly = true;
228  start();
229  mPrepared = true;
230 }
231 
233 {
234  if ( !mPrepared )
235  return;
236 
237  doRender();
238  futureFinished();
239  mRenderSynchronously = false;
240  mPrepareOnly = false;
241  mPrepared = false;
242 }
243 
244 void QgsMapRendererCustomPainterJob::futureFinished()
245 {
246  mActive = false;
247  if ( !mPrepared ) // can't access from other thread
248  mRenderingTime = mRenderingStart.elapsed();
249  QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
250 
251  if ( !mPrepared )
252  logRenderingTime( mLayerJobs, {}, mLabelJob );
253 
254  // final cleanup
255  cleanupJobs( mLayerJobs );
256  cleanupSecondPassJobs( mSecondPassLayerJobs );
257  cleanupLabelJob( mLabelJob );
258 
259  emit finished();
260 }
261 
262 
263 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
264 {
265  try
266  {
267  self->doRender();
268  }
269  catch ( QgsException &e )
270  {
271  Q_UNUSED( e )
272  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
273  }
274  catch ( std::exception &e )
275  {
276  Q_UNUSED( e )
277  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
278  }
279  catch ( ... )
280  {
281  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
282  }
283 }
284 
285 void QgsMapRendererCustomPainterJob::doRender()
286 {
287  bool hasSecondPass = ! mSecondPassLayerJobs.isEmpty();
288  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
289  QElapsedTimer renderTime;
290  renderTime.start();
291 
292  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
293  {
294  LayerRenderJob &job = *it;
295 
296  if ( job.context.renderingStopped() )
297  break;
298 
299  if ( ! hasSecondPass && job.context.useAdvancedEffects() )
300  {
301  // Set the QPainter composition mode so that this layer is rendered using
302  // the desired blending mode
303  mPainter->setCompositionMode( job.blendMode );
304  }
305 
306  if ( !job.cached )
307  {
308  QElapsedTimer layerTime;
309  layerTime.start();
310 
311  if ( job.img )
312  {
313  job.img->fill( 0 );
314  job.imageInitialized = true;
315  }
316 
317  job.renderer->render();
318 
319  job.renderingTime += layerTime.elapsed();
320  }
321 
322  if ( ! hasSecondPass && job.img )
323  {
324  // If we flattened this layer for alternate blend modes, composite it now
325  mPainter->setOpacity( job.opacity );
326  mPainter->drawImage( 0, 0, *job.img );
327  mPainter->setOpacity( 1.0 );
328  }
329 
330  }
331 
332  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
333 
334  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
335  {
336  if ( !mLabelJob.cached )
337  {
338  QElapsedTimer labelTime;
339  labelTime.start();
340 
341  if ( mLabelJob.img )
342  {
343  QPainter painter;
344  mLabelJob.img->fill( 0 );
345  painter.begin( mLabelJob.img );
346  mLabelJob.context.setPainter( &painter );
347  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
348  painter.end();
349  }
350  else
351  {
352  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
353  }
354 
355  mLabelJob.complete = true;
356  mLabelJob.renderingTime = labelTime.elapsed();
357  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
358  }
359  }
360 
361  if ( ! hasSecondPass )
362  {
363  if ( mLabelJob.img && mLabelJob.complete )
364  {
365  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
366  mPainter->setOpacity( 1.0 );
367  mPainter->drawImage( 0, 0, *mLabelJob.img );
368  }
369  }
370  else
371  {
372  for ( LayerRenderJob &job : mSecondPassLayerJobs )
373  {
374  if ( job.context.renderingStopped() )
375  break;
376 
377  if ( !job.cached )
378  {
379  QElapsedTimer layerTime;
380  layerTime.start();
381 
382  if ( job.img )
383  {
384  job.img->fill( 0 );
385  job.imageInitialized = true;
386  }
387 
388  job.renderer->render();
389 
390  job.renderingTime += layerTime.elapsed();
391  }
392  }
393 
394  composeSecondPass( mSecondPassLayerJobs, mLabelJob );
395 
396  QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
397 
398  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
399  mPainter->setOpacity( 1.0 );
400  mPainter->drawImage( 0, 0, finalImage );
401  }
402 
403  QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
404 }
405 
406 
QgsException
Defines a QGIS exception class.
Definition: qgsexception.h:35
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:172
QgsMapRendererJob::mErrors
Errors mErrors
Definition: qgsmaprendererjob.h:317
QgsMapRendererAbstractCustomPainterJob
Abstract base class for map renderer jobs which use custom painters.
Definition: qgsmaprenderercustompainterjob.h:32
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:247
QgsMapRendererCustomPainterJob::isActive
bool isActive() const override
Tell whether the rendering job is currently running in background.
Definition: qgsmaprenderercustompainterjob.cpp:189
QgsLabelingResults
Class that stores computed placement from labeling engine.
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:337
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:131
QgsMapRendererCustomPainterJob::start
void start() override
Start the rendering job and immediately return.
Definition: qgsmaprenderercustompainterjob.cpp:78
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:199
QgsMapSettings::DrawLabeling
@ DrawLabeling
Enable drawing of labels on top of the map.
Definition: qgsmapsettings.h:307
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:208
QgsMapRendererJob::mRenderingTime
int mRenderingTime
Definition: qgsmaprendererjob.h:321
QgsMapRendererCustomPainterJob::cancelWithoutBlocking
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
Definition: qgsmaprenderercustompainterjob.cpp:155
QgsMapRendererCustomPainterJob::usedCachedLabels
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
Definition: qgsmaprenderercustompainterjob.cpp:194
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:303
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:293
QgsMapSettings::LosslessImageRendering
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
Definition: qgsmapsettings.h:315
QgsMapRendererCustomPainterJob::QgsMapRendererCustomPainterJob
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
Definition: qgsmaprenderercustompainterjob.cpp:62
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:224
QgsMapRendererCustomPainterJob
Job implementation that renders everything sequentially using a custom painter.
Definition: qgsmaprenderercustompainterjob.h:64
QgsMapRendererCustomPainterJob::renderSynchronously
void renderSynchronously()
Render the map synchronously in this thread.
Definition: qgsmaprenderercustompainterjob.cpp:216
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:258
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:71
QgsMapSettings::testFlag
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
Definition: qgsmapsettings.cpp:355
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
Abstract base class for map rendering implementations.
Definition: qgsmaprendererjob.h:187
QgsMapRendererCustomPainterJob::renderPrepared
void renderPrepared()
Render a pre-prepared job.
Definition: qgsmaprenderercustompainterjob.cpp:232
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:263
QgsMapRendererJob::prepareSecondPassJobs
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
QgsMapSettings
The QgsMapSettings class contains configuration for rendering of the map.
Definition: qgsmapsettings.h:88
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)