QGIS API Documentation  3.21.0-Master (5b68dc587e)
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( Qgis::MapSettingsFlag::Antialiasing ) );
43 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
44  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
45 #endif
46 
47 #ifndef QT_NO_DEBUG
48  QPaintDevice *paintDevice = painter->device();
49  const 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 
78 void QgsMapRendererCustomPainterJob::startPrivate()
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  const 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 ( LayerRenderJob &job : mLayerJobs )
165  {
166  job.context()->setRenderingStopped( true );
167  if ( job.renderer && job.renderer->feedback() )
168  job.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  const bool hasSecondPass = ! mSecondPassLayerJobs.empty();
288  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
289  QElapsedTimer renderTime;
290  renderTime.start();
291 
292  for ( LayerRenderJob &job : mLayerJobs )
293  {
294  if ( job.context()->renderingStopped() )
295  break;
296 
297  if ( ! hasSecondPass && job.context()->useAdvancedEffects() )
298  {
299  // Set the QPainter composition mode so that this layer is rendered using
300  // the desired blending mode
301  mPainter->setCompositionMode( job.blendMode );
302  }
303 
304  if ( !job.cached )
305  {
306  QElapsedTimer layerTime;
307  layerTime.start();
308 
309  if ( job.img )
310  {
311  job.img->fill( 0 );
312  job.imageInitialized = true;
313  }
314 
315  job.completed = job.renderer->render();
316 
317  job.renderingTime += layerTime.elapsed();
318  }
319 
320  if ( ! hasSecondPass && job.img )
321  {
322  // If we flattened this layer for alternate blend modes, composite it now
323  mPainter->setOpacity( job.opacity );
324  mPainter->drawImage( 0, 0, *job.img );
325  mPainter->setOpacity( 1.0 );
326  }
327 
328  }
329 
330  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
331 
332  if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
333  {
334  if ( !mLabelJob.cached )
335  {
336  QElapsedTimer labelTime;
337  labelTime.start();
338 
339  if ( mLabelJob.img )
340  {
341  QPainter painter;
342  mLabelJob.img->fill( 0 );
343  painter.begin( mLabelJob.img );
344  mLabelJob.context.setPainter( &painter );
345  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
346  painter.end();
347  }
348  else
349  {
350  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
351  }
352 
353  mLabelJob.complete = true;
354  mLabelJob.renderingTime = labelTime.elapsed();
355  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
356  }
357  }
358 
359  if ( ! hasSecondPass )
360  {
361  if ( mLabelJob.img && mLabelJob.complete )
362  {
363  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
364  mPainter->setOpacity( 1.0 );
365  mPainter->drawImage( 0, 0, *mLabelJob.img );
366  }
367  }
368  else
369  {
370  for ( LayerRenderJob &job : mSecondPassLayerJobs )
371  {
372  if ( job.context()->renderingStopped() )
373  break;
374 
375  if ( !job.cached )
376  {
377  QElapsedTimer layerTime;
378  layerTime.start();
379 
380  if ( job.img )
381  {
382  job.img->fill( 0 );
383  job.imageInitialized = true;
384  }
385 
386  job.completed = job.renderer->render();
387 
388  job.renderingTime += layerTime.elapsed();
389  }
390  }
391 
392  composeSecondPass( mSecondPassLayerJobs, mLabelJob );
393 
394  const QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
395 
396  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
397  mPainter->setOpacity( 1.0 );
398  mPainter->drawImage( 0, 0, finalImage );
399  }
400 
401  QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
402 }
403 
404 
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Enable anti-aliasing for map rendering.
@ DrawLabeling
Enable drawing of labels on top of the map.
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
Defines a QGIS exception class.
Definition: qgsexception.h:35
QString what() const
Definition: qgsexception.h:48
Class that stores computed placement from labeling engine.
Abstract base class for map renderer jobs which use custom painters.
void preparePainter(QPainter *painter, const QColor &backgroundColor=Qt::transparent)
Prepares the given painter ready for a map render.
QgsMapRendererAbstractCustomPainterJob(const QgsMapSettings &settings)
Constructor for QgsMapRendererAbstractCustomPainterJob, using the given map settings.
Job implementation that renders everything sequentially using a custom painter.
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void renderSynchronously()
Render the map synchronously in this thread.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
void waitForFinishedWithEventLoop(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Wait for the job to be finished - and keep the thread's event loop running while waiting.
void renderPrepared()
Render a pre-prepared job.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
void prepare()
Prepares the job for rendering synchronously in a background thread.
void waitForFinished() override
Block until the job has finished.
bool isActive() const override
Tell whether the rendering job is currently running in background.
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
Abstract base class for map rendering implementations.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
static QImage composeImage(const QgsMapSettings &settings, const std::vector< LayerRenderJob > &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
void cleanupSecondPassJobs(std::vector< LayerRenderJob > &jobs)
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
QElapsedTimer mRenderingStart
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapSettings mSettings
void start()
Start the rendering job and immediately return.
std::vector< LayerRenderJob > prepareSecondPassJobs(std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
The QgsMapSettings class contains configuration for rendering of the map.
QSize deviceOutputSize() const
Returns the device output size of the map render.
QColor backgroundColor() const
Returns the background color of the map.
float devicePixelRatio() const
Returns the device pixel ratio.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1234
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38