QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
24 #include <QtConcurrentRun>
25 
26 //
27 // QgsMapRendererAbstractCustomPainterJob
28 //
29 
31  : QgsMapRendererJob( settings )
32 {
33 
34 }
35 
36 void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
37 {
38  // clear the background
39  painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
40 
41  painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
42 
43 #ifndef QT_NO_DEBUG
44  QPaintDevice *paintDevice = painter->device();
45  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
46  .arg( paintDevice->logicalDpiX() )
48  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() * mSettings.devicePixelRatio() ),
49  "Job::startRender()", errMsg.toLatin1().data() );
50 #endif
51 }
52 
53 
54 //
55 // QgsMapRendererCustomPainterJob
56 //
57 
60  , mPainter( painter )
61  , mActive( false )
62  , mRenderSynchronously( false )
63 {
64  QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
65 }
66 
68 {
69  QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
70  Q_ASSERT( !mFutureWatcher.isRunning() );
71  //cancel();
72 }
73 
75 {
76  if ( isActive() )
77  return;
78 
79  if ( !mPrepareOnly )
80  mRenderingStart.start();
81 
82  mActive = true;
83 
84  mErrors.clear();
85 
86  QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
87 
88  QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
89  QTime prepareTime;
90  prepareTime.start();
91 
93 
94  mLabelingEngineV2.reset();
95 
97  {
98  mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
99  mLabelingEngineV2->setMapSettings( mSettings );
100  }
101 
102  bool canUseLabelCache = prepareLabelCache();
103  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
104  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
105 
106  QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
107 
108  if ( mRenderSynchronously )
109  {
110  if ( !mPrepareOnly )
111  {
112  // do the rendering right now!
113  doRender();
114  }
115  return;
116  }
117 
118  // now we are ready to start rendering!
119  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
120 
121  mFuture = QtConcurrent::run( staticRender, this );
122  mFutureWatcher.setFuture( mFuture );
123 }
124 
125 
127 {
128  if ( !isActive() )
129  {
130  QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
131  return;
132  }
133 
134  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
135  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
137 
138  QTime t;
139  t.start();
140 
141  mFutureWatcher.waitForFinished();
142 
143  QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
144 
145  futureFinished();
146 
147  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
148 }
149 
151 {
152  if ( !isActive() )
153  {
154  QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
155  return;
156  }
157 
158  mLabelJob.context.setRenderingStopped( true );
159  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
160  {
161  it->context.setRenderingStopped( true );
162  if ( it->renderer && it->renderer->feedback() )
163  it->renderer->feedback()->cancel();
164  }
165 }
166 
168 {
169  if ( !isActive() )
170  return;
171 
172  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
173 
174  QTime t;
175  t.start();
176 
177  mFutureWatcher.waitForFinished();
178 
179  QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
180 
181  futureFinished();
182 }
183 
185 {
186  return mActive;
187 }
188 
190 {
191  return mLabelJob.cached;
192 }
193 
195 {
196  if ( mLabelingEngineV2 )
197  return mLabelingEngineV2->takeResults();
198  else
199  return nullptr;
200 }
201 
202 
203 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
204 {
205  QEventLoop loop;
206  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
207  loop.exec( flags );
208 }
209 
210 
212 {
213  mRenderSynchronously = true;
214  start();
215  futureFinished();
216  mRenderSynchronously = false;
217 }
218 
220 {
221  mRenderSynchronously = true;
222  mPrepareOnly = true;
223  start();
224  mPrepared = true;
225 }
226 
228 {
229  if ( !mPrepared )
230  return;
231 
232  doRender();
233  futureFinished();
234  mRenderSynchronously = false;
235  mPrepareOnly = false;
236  mPrepared = false;
237 }
238 
239 void QgsMapRendererCustomPainterJob::futureFinished()
240 {
241  mActive = false;
242  if ( !mPrepared ) // can't access from other thread
243  mRenderingTime = mRenderingStart.elapsed();
244  QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
245 
246  if ( !mPrepared )
247  logRenderingTime( mLayerJobs, mLabelJob );
248 
249  // final cleanup
250  cleanupJobs( mLayerJobs );
251  cleanupLabelJob( mLabelJob );
252 
253  emit finished();
254 }
255 
256 
257 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
258 {
259  try
260  {
261  self->doRender();
262  }
263  catch ( QgsException &e )
264  {
265  Q_UNUSED( e )
266  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
267  }
268  catch ( std::exception &e )
269  {
270  Q_UNUSED( e )
271  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
272  }
273  catch ( ... )
274  {
275  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
276  }
277 }
278 
279 void QgsMapRendererCustomPainterJob::doRender()
280 {
281  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
282  QTime renderTime;
283  renderTime.start();
284 
285  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
286  {
287  LayerRenderJob &job = *it;
288 
289  if ( job.context.renderingStopped() )
290  break;
291 
292  if ( job.context.useAdvancedEffects() )
293  {
294  // Set the QPainter composition mode so that this layer is rendered using
295  // the desired blending mode
296  mPainter->setCompositionMode( job.blendMode );
297  }
298 
299  if ( !job.cached )
300  {
301  QTime layerTime;
302  layerTime.start();
303 
304  if ( job.img )
305  {
306  job.img->fill( 0 );
307  job.imageInitialized = true;
308  }
309 
310  job.renderer->render();
311 
312  job.renderingTime += layerTime.elapsed();
313  }
314 
315  if ( job.img )
316  {
317  // If we flattened this layer for alternate blend modes, composite it now
318  mPainter->setOpacity( job.opacity );
319  mPainter->drawImage( 0, 0, *job.img );
320  mPainter->setOpacity( 1.0 );
321  }
322 
323  }
324 
325  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
326 
327  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
328  {
329  if ( !mLabelJob.cached )
330  {
331  QTime labelTime;
332  labelTime.start();
333 
334  if ( mLabelJob.img )
335  {
336  QPainter painter;
337  mLabelJob.img->fill( 0 );
338  painter.begin( mLabelJob.img );
339  mLabelJob.context.setPainter( &painter );
340  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
341  painter.end();
342  }
343  else
344  {
345  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
346  }
347 
348  mLabelJob.complete = true;
349  mLabelJob.renderingTime = labelTime.elapsed();
350  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
351  }
352  }
353  if ( mLabelJob.img && mLabelJob.complete )
354  {
355  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
356  mPainter->setOpacity( 1.0 );
357  mPainter->drawImage( 0, 0, *mLabelJob.img );
358  }
359 
360  QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
361 }
362 
363 
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.
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 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:280
QColor backgroundColor() const
Gets the background color of the map.
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.
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.
void logRenderingTime(const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
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.