QGIS API Documentation  3.25.0-Master (6b426f5f8a)
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_p.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  painter->setRenderHint( QPainter::SmoothPixmapTransform, mSettings.testFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms ) );
44 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
45  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
46 #endif
47 
48 #ifndef QT_NO_DEBUG
49  QPaintDevice *paintDevice = painter->device();
50  const QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
51  .arg( paintDevice->logicalDpiX() )
52  .arg( mSettings.outputDpi() );
53  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi(), 1.0 ),
54  "Job::startRender()", errMsg.toLatin1().data() );
55 #endif
56 }
57 
58 
59 //
60 // QgsMapRendererCustomPainterJob
61 //
62 
65  , mPainter( painter )
66  , mActive( false )
67  , mRenderSynchronously( false )
68 {
69  QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
70 }
71 
73 {
74  QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
75  Q_ASSERT( !mFutureWatcher.isRunning() );
76  //cancel();
77 }
78 
79 void QgsMapRendererCustomPainterJob::startPrivate()
80 {
81  if ( isActive() )
82  return;
83 
84  if ( !mPrepareOnly )
85  mRenderingStart.start();
86 
87  mActive = true;
88 
89  mErrors.clear();
90 
91  QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
92 
93  QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
94  QElapsedTimer prepareTime;
95  prepareTime.start();
96 
98 
99  mLabelingEngineV2.reset();
100 
102  {
103  mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
104  mLabelingEngineV2->setMapSettings( mSettings );
105  }
106 
107  const bool canUseLabelCache = prepareLabelCache();
108  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
109  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
110  mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
111 
112  QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
113 
114  if ( mRenderSynchronously )
115  {
116  if ( !mPrepareOnly )
117  {
118  // do the rendering right now!
119  doRender();
120  }
121  return;
122  }
123 
124  // now we are ready to start rendering!
125  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
126 
127  mFuture = QtConcurrent::run( staticRender, this );
128  mFutureWatcher.setFuture( mFuture );
129 }
130 
131 
133 {
134  if ( !isActive() )
135  {
136  QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
137  return;
138  }
139 
140  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
141  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
143 
144  QElapsedTimer t;
145  t.start();
146 
147  mFutureWatcher.waitForFinished();
148 
149  QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
150 
151  futureFinished();
152 
153  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
154 }
155 
157 {
158  if ( !isActive() )
159  {
160  QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
161  return;
162  }
163 
164  mLabelJob.context.setRenderingStopped( true );
165  for ( LayerRenderJob &job : mLayerJobs )
166  {
167  job.context()->setRenderingStopped( true );
168  if ( job.renderer && job.renderer->feedback() )
169  job.renderer->feedback()->cancel();
170  }
171 }
172 
174 {
175  if ( !isActive() )
176  return;
177 
178  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
179 
180  QElapsedTimer t;
181  t.start();
182 
183  mFutureWatcher.waitForFinished();
184 
185  QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
186 
187  futureFinished();
188 }
189 
191 {
192  return mActive;
193 }
194 
196 {
197  return mLabelJob.cached;
198 }
199 
201 {
202  if ( mLabelingEngineV2 )
203  return mLabelingEngineV2->takeResults();
204  else
205  return nullptr;
206 }
207 
208 
209 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
210 {
211  QEventLoop loop;
212  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
213  loop.exec( flags );
214 }
215 
216 
218 {
219  mRenderSynchronously = true;
220  start();
221  futureFinished();
222  mRenderSynchronously = false;
223 }
224 
226 {
227  mRenderSynchronously = true;
228  mPrepareOnly = true;
229  start();
230  mPrepared = true;
231 }
232 
234 {
235  if ( !mPrepared )
236  return;
237 
238  doRender();
239  futureFinished();
240  mRenderSynchronously = false;
241  mPrepareOnly = false;
242  mPrepared = false;
243 }
244 
245 void QgsMapRendererCustomPainterJob::futureFinished()
246 {
247  mActive = false;
248  if ( !mPrepared ) // can't access from other thread
249  mRenderingTime = mRenderingStart.elapsed();
250  QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
251 
252  if ( !mPrepared )
253  logRenderingTime( mLayerJobs, {}, mLabelJob );
254 
255  // final cleanup
256  cleanupJobs( mLayerJobs );
257  cleanupSecondPassJobs( mSecondPassLayerJobs );
258  cleanupLabelJob( mLabelJob );
259 
260  emit finished();
261 }
262 
263 
264 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
265 {
266  try
267  {
268  self->doRender();
269  }
270  catch ( QgsException &e )
271  {
272  Q_UNUSED( e )
273  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
274  }
275  catch ( std::exception &e )
276  {
277  Q_UNUSED( e )
278  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
279  }
280  catch ( ... )
281  {
282  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
283  }
284 }
285 
286 void QgsMapRendererCustomPainterJob::doRender()
287 {
288  const bool hasSecondPass = ! mSecondPassLayerJobs.empty();
289  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
290  QElapsedTimer renderTime;
291  renderTime.start();
292 
293  for ( LayerRenderJob &job : mLayerJobs )
294  {
295  if ( job.context()->renderingStopped() )
296  break;
297 
298  emit layerRenderingStarted( job.layerId );
299 
300  if ( ! hasSecondPass && job.context()->useAdvancedEffects() )
301  {
302  // Set the QPainter composition mode so that this layer is rendered using
303  // the desired blending mode
304  mPainter->setCompositionMode( job.blendMode );
305  }
306 
307  if ( !job.cached )
308  {
309  QElapsedTimer layerTime;
310  layerTime.start();
311 
312  if ( job.img )
313  {
314  job.img->fill( 0 );
315  job.imageInitialized = true;
316  }
317 
318  job.completed = job.renderer->render();
319 
320  job.renderingTime += layerTime.elapsed();
321  }
322 
323  if ( ! hasSecondPass && job.img )
324  {
325  // If we flattened this layer for alternate blend modes, composite it now
326  mPainter->setOpacity( job.opacity );
327  mPainter->drawImage( 0, 0, *job.img );
328  mPainter->setOpacity( 1.0 );
329  }
330 
331  emit layerRendered( job.layerId );
332  }
333 
335  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
336 
337  if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
338  {
339  if ( !mLabelJob.cached )
340  {
341  QElapsedTimer labelTime;
342  labelTime.start();
343 
344  if ( mLabelJob.img )
345  {
346  QPainter painter;
347  mLabelJob.img->fill( 0 );
348  painter.begin( mLabelJob.img );
349  mLabelJob.context.setPainter( &painter );
350  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
351  painter.end();
352  }
353  else
354  {
355  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
356  }
357 
358  mLabelJob.complete = true;
359  mLabelJob.renderingTime = labelTime.elapsed();
360  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
361  }
362  }
363 
364  if ( ! hasSecondPass )
365  {
366  if ( mLabelJob.img && mLabelJob.complete )
367  {
368  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
369  mPainter->setOpacity( 1.0 );
370  mPainter->drawImage( 0, 0, *mLabelJob.img );
371  }
372  }
373  else
374  {
375  for ( LayerRenderJob &job : mSecondPassLayerJobs )
376  {
377  if ( job.context()->renderingStopped() )
378  break;
379 
380  if ( !job.cached )
381  {
382  QElapsedTimer layerTime;
383  layerTime.start();
384 
385  if ( job.img )
386  {
387  job.img->fill( 0 );
388  job.imageInitialized = true;
389  }
390 
391  job.completed = job.renderer->render();
392 
393  job.renderingTime += layerTime.elapsed();
394  }
395  }
396 
397  composeSecondPass( mSecondPassLayerJobs, mLabelJob );
398 
399  const QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
400 
401  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
402  mPainter->setOpacity( 1.0 );
403  mPainter->drawImage( 0, 0, finalImage );
404  }
405 
406  QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
407 }
408 
409 
@ 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.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
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)
void layerRendered(const QString &layerId)
Emitted when a layer has completed rendering.
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 renderingLayersFinished()
Emitted when the layers are rendered.
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.
void layerRenderingStarted(const QString &layerId)
Emitted just before rendering starts for a particular layer.
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.
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:1978
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38