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