QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
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  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.completed = 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.completed = 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 
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 cleanupSecondPassJobs(LayerRenderJobs &jobs)
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
QElapsedTimer mRenderingStart
void finished()
emitted when asynchronous rendering is finished (or canceled).
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
QgsMapSettings mSettings
void start()
Start the rendering job and immediately return.
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...
void cleanupJobs(LayerRenderJobs &jobs)
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.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
@ 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.
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 qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38