QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void finished()
emitted when asynchronous rendering is finished (or canceled).
Job implementation that renders everything sequentially using a custom painter.
QElapsedTimer mRenderingStart
Abstract base class for map rendering implementations.
void cleanupJobs(LayerRenderJobs &jobs)
QSize deviceOutputSize() const
Returns the device output size of the map canvas This is equivalent to the output size multiplicated ...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
void preparePainter(QPainter *painter, const QColor &backgroundColor=Qt::transparent)
Prepares the given painter ready for a map render.
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
QColor backgroundColor() const
Gets the background color of the map.
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
void cancel() override
Stop the rendering job - does not return until the job has terminated.
void prepare()
Prepares the job for rendering synchronously in a background thread.
void waitForFinishedWithEventLoop(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Wait for the job to be finished - and keep the thread&#39;s event loop running while waiting.
Enable drawing of labels on top of the map.
QString what() const
Definition: qgsexception.h:48
The QgsMapSettings class contains configuration for rendering of the map.
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void start() override
Start the rendering job and immediately return.
void renderPrepared()
Render a pre-prepared job.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
Abstract base class for map renderer jobs which use custom painters.
Enable anti-aliasing for map rendering.
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
float devicePixelRatio() const
Returns device pixel ratio Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" di...
QgsMapSettings mSettings
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
double outputDpi() const
Returns DPI used for conversion between real world units (e.g.
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
Class that stores computed placement from labeling engine.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results). ...
QgsMapRendererAbstractCustomPainterJob(const QgsMapSettings &settings)
Constructor for QgsMapRendererAbstractCustomPainterJob, using the given map settings.
void waitForFinished() override
Block until the job has finished.
Defines a QGIS exception class.
Definition: qgsexception.h:34
void renderSynchronously()
Render the map synchronously in this thread.
bool isActive() const override
Tell whether the rendering job is currently running in background.