QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 "qgsproject.h"
22 #include "qgsmaplayerrenderer.h"
23 #include "qgsvectorlayer.h"
24 #include "qgsrenderer.h"
25 #include "qgsmaplayerlistutils.h"
26 
27 #include <QtConcurrentRun>
28 
30  : QgsMapRendererJob( settings )
31  , mPainter( painter )
32  , mActive( false )
33  , mRenderSynchronously( false )
34 {
35  QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
36 }
37 
39 {
40  QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
41  Q_ASSERT( !mFutureWatcher.isRunning() );
42  //cancel();
43 }
44 
46 {
47  if ( isActive() )
48  return;
49 
50  mRenderingStart.start();
51 
52  mActive = true;
53 
54  mErrors.clear();
55 
56  QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
57 
58  QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
59  QTime prepareTime;
60  prepareTime.start();
61 
62  // clear the background
63  mPainter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), mSettings.backgroundColor() );
64 
65  mPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
66 
67 #ifndef QT_NO_DEBUG
68  QPaintDevice *paintDevice = mPainter->device();
69  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
70  .arg( paintDevice->logicalDpiX() )
72  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() * mSettings.devicePixelRatio() ),
73  "Job::startRender()", errMsg.toLatin1().data() );
74 #endif
75 
76  mLabelingEngineV2.reset();
77 
79  {
80  mLabelingEngineV2.reset( new QgsLabelingEngine() );
81  mLabelingEngineV2->setMapSettings( mSettings );
82  }
83 
84  bool canUseLabelCache = prepareLabelCache();
85  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
86  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
87 
88  QgsDebugMsgLevel( "Rendering prepared in (seconds): " + QString( "%1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
89 
90  if ( mRenderSynchronously )
91  {
92  // do the rendering right now!
93  doRender();
94  return;
95  }
96 
97  // now we are ready to start rendering!
98  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
99 
100  mFuture = QtConcurrent::run( staticRender, this );
101  mFutureWatcher.setFuture( mFuture );
102 }
103 
104 
106 {
107  if ( !isActive() )
108  {
109  QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
110  return;
111  }
112 
113  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
114  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
116 
117  QTime t;
118  t.start();
119 
120  mFutureWatcher.waitForFinished();
121 
122  QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
123 
124  futureFinished();
125 
126  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
127 }
128 
130 {
131  if ( !isActive() )
132  {
133  QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
134  return;
135  }
136 
137  mLabelJob.context.setRenderingStopped( true );
138  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
139  {
140  it->context.setRenderingStopped( true );
141  if ( it->renderer && it->renderer->feedback() )
142  it->renderer->feedback()->cancel();
143  }
144 }
145 
147 {
148  if ( !isActive() )
149  return;
150 
151  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
152 
153  QTime t;
154  t.start();
155 
156  mFutureWatcher.waitForFinished();
157 
158  QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
159 
160  futureFinished();
161 }
162 
164 {
165  return mActive;
166 }
167 
169 {
170  return mLabelJob.cached;
171 }
172 
174 {
175  if ( mLabelingEngineV2 )
176  return mLabelingEngineV2->takeResults();
177  else
178  return nullptr;
179 }
180 
181 
182 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
183 {
184  QEventLoop loop;
185  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
186  loop.exec( flags );
187 }
188 
189 
191 {
192  mRenderSynchronously = true;
193  start();
194  futureFinished();
195  mRenderSynchronously = false;
196 }
197 
198 
199 void QgsMapRendererCustomPainterJob::futureFinished()
200 {
201  mActive = false;
202  mRenderingTime = mRenderingStart.elapsed();
203  QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
204 
205  logRenderingTime( mLayerJobs, mLabelJob );
206 
207  // final cleanup
208  cleanupJobs( mLayerJobs );
209  cleanupLabelJob( mLabelJob );
210 
211  emit finished();
212 }
213 
214 
215 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
216 {
217  try
218  {
219  self->doRender();
220  }
221  catch ( QgsException &e )
222  {
223  Q_UNUSED( e );
224  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
225  }
226  catch ( std::exception &e )
227  {
228  Q_UNUSED( e );
229  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
230  }
231  catch ( ... )
232  {
233  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
234  }
235 }
236 
237 void QgsMapRendererCustomPainterJob::doRender()
238 {
239  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
240  QTime renderTime;
241  renderTime.start();
242 
243  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
244  {
245  LayerRenderJob &job = *it;
246 
247  if ( job.context.renderingStopped() )
248  break;
249 
250  if ( job.context.useAdvancedEffects() )
251  {
252  // Set the QPainter composition mode so that this layer is rendered using
253  // the desired blending mode
254  mPainter->setCompositionMode( job.blendMode );
255  }
256 
257  if ( !job.cached )
258  {
259  QTime layerTime;
260  layerTime.start();
261 
262  if ( job.img )
263  {
264  job.img->fill( 0 );
265  job.imageInitialized = true;
266  }
267 
268  job.renderer->render();
269 
270  job.renderingTime += layerTime.elapsed();
271  }
272 
273  if ( job.img )
274  {
275  // If we flattened this layer for alternate blend modes, composite it now
276  mPainter->setOpacity( job.opacity );
277  mPainter->drawImage( 0, 0, *job.img );
278  mPainter->setOpacity( 1.0 );
279  }
280 
281  }
282 
283  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
284 
285  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
286  {
287  if ( !mLabelJob.cached )
288  {
289  QTime labelTime;
290  labelTime.start();
291 
292  if ( mLabelJob.img )
293  {
294  QPainter painter;
295  mLabelJob.img->fill( 0 );
296  painter.begin( mLabelJob.img );
297  mLabelJob.context.setPainter( &painter );
298  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
299  painter.end();
300  }
301  else
302  {
303  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
304  }
305 
306  mLabelJob.complete = true;
307  mLabelJob.renderingTime = labelTime.elapsed();
308  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
309  }
310  }
311  if ( mLabelJob.img && mLabelJob.complete )
312  {
313  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
314  mPainter->setOpacity( 1.0 );
315  mPainter->drawImage( 0, 0, *mLabelJob.img );
316  }
317 
318  QgsDebugMsgLevel( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
319 }
320 
321 void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
322 {
323  QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
324 
325  QTime t;
326  t.start();
327 
328  // Reset the composition mode before rendering the labels
329  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
330 
331  renderContext.setPainter( painter );
332 
333  if ( labelingEngine2 )
334  {
335  labelingEngine2->run( renderContext );
336  }
337 
338  QgsDebugMsg( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ) );
339 }
340 
341 void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
342 {
343  Q_UNUSED( settings );
344 
345  drawLabeling( renderContext, labelingEngine2, painter );
346 }
347 
348 bool QgsMapRendererJob::needTemporaryImage( QgsMapLayer *ml )
349 {
350  if ( ml->type() == QgsMapLayer::VectorLayer )
351  {
352  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
353  if ( vl->renderer() && vl->renderer()->forceRasterRender() )
354  {
355  //raster rendering is forced for this layer
356  return true;
357  }
359  ( ( vl->blendMode() != QPainter::CompositionMode_SourceOver )
360  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
361  || ( !qgsDoubleNear( vl->opacity(), 1.0 ) ) ) )
362  {
363  //layer properties require rasterization
364  return true;
365  }
366  }
367  else if ( ml->type() == QgsMapLayer::RasterLayer )
368  {
369  // preview of intermediate raster rendering results requires a temporary output image
371  return true;
372  }
373 
374  return false;
375 }
376 
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void finished()
emitted when asynchronous rendering is finished (or canceled).
Base class for all map layer types.
Definition: qgsmaplayer.h:63
Job implementation that renders everything sequentially using a custom painter.
QgsMapLayer::LayerType type() const
Returns the type of the layer.
Abstract base class for map rendering implementations.
void cleanupJobs(LayerRenderJobs &jobs)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
float devicePixelRatio() const
Returns device pixel ratio Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" di...
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
void cancel() override
Stop the rendering job - does not return until the job has terminated.
Enable layer opacity and blending effects.
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.
The QgsMapSettings class contains configuration for rendering of the map.
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
void start() override
Start the rendering job and immediately return.
QPainter::CompositionMode featureBlendMode() const
Returns the current blending mode for features.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
Enable anti-aliasing for map rendering.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsFeatureRenderer * renderer()
Returns renderer.
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
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.
QColor backgroundColor() const
Gets the background color of the map.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
The QgsLabelingEngine class provides map labeling functionality.
void run(QgsRenderContext &context)
compute the labeling with given map settings and providers
QString what() const
Definition: qgsexception.h:48
Contains information about the context of a rendering operation.
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2)
void logRenderingTime(const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
bool forceRasterRender() const
Returns whether the renderer must render as a raster.
Definition: qgsrenderer.h:400
Class that stores computed placement from labeling engine.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results). ...
double outputDpi() const
Returns DPI used for conversion between real world units (e.g.
void waitForFinished() override
Block until the job has finished.
Represents a vector layer which manages a vector based data sets.
Defines a QGIS exception class.
Definition: qgsexception.h:34
void renderSynchronously()
Render the map synchronously in this thread.
QSize deviceOutputSize() const
Returns the device output size of the map canvas This is equivalent to the output size multiplicated ...
bool isActive() const override
Tell whether the rendering job is currently running in background.