QGIS API Documentation  2.99.0-Master (9ed189e)
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 "qgspallabeling.h"
24 #include "qgsvectorlayer.h"
25 #include "qgsrenderer.h"
26 #include "qgsmaplayerlistutils.h"
27 
29  : QgsMapRendererJob( settings )
30  , mPainter( painter )
31  , mActive( false )
32  , mRenderSynchronously( false )
33 {
34  QgsDebugMsg( "QPAINTER construct" );
35 }
36 
38 {
39  QgsDebugMsg( "QPAINTER destruct" );
40  Q_ASSERT( !mFutureWatcher.isRunning() );
41  //cancel();
42 }
43 
45 {
46  if ( isActive() )
47  return;
48 
49  mRenderingStart.start();
50 
51  mActive = true;
52 
53  mErrors.clear();
54 
55  QgsDebugMsg( "QPAINTER run!" );
56 
57  QgsDebugMsg( "Preparing list of layer jobs for rendering" );
58  QTime prepareTime;
59  prepareTime.start();
60 
61  // clear the background
62  mPainter->fillRect( 0, 0, mSettings.outputSize().width(), mSettings.outputSize().height(), mSettings.backgroundColor() );
63 
64  mPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
65 
66 #ifndef QT_NO_DEBUG
67  QPaintDevice* thePaintDevice = mPainter->device();
68  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" ).arg( thePaintDevice->logicalDpiX() ).arg( mSettings.outputDpi() );
69  Q_ASSERT_X( qgsDoubleNear( thePaintDevice->logicalDpiX(), mSettings.outputDpi() ), "Job::startRender()", errMsg.toLatin1().data() );
70 #endif
71 
72  mLabelingEngineV2.reset();
73 
75  {
76  mLabelingEngineV2.reset( new QgsLabelingEngine() );
77  mLabelingEngineV2->readSettingsFromProject( QgsProject::instance() );
78  mLabelingEngineV2->setMapSettings( mSettings );
79  }
80 
81  bool canUseLabelCache = prepareLabelCache();
82  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
83  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
84 
85  QgsDebugMsg( "Rendering prepared in (seconds): " + QString( "%1" ).arg( prepareTime.elapsed() / 1000.0 ) );
86 
87  if ( mRenderSynchronously )
88  {
89  // do the rendering right now!
90  doRender();
91  return;
92  }
93 
94  // now we are ready to start rendering!
95  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
96 
97  mFuture = QtConcurrent::run( staticRender, this );
98  mFutureWatcher.setFuture( mFuture );
99 }
100 
101 
103 {
104  if ( !isActive() )
105  {
106  QgsDebugMsg( "QPAINTER not running!" );
107  return;
108  }
109 
110  QgsDebugMsg( "QPAINTER canceling" );
111  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
112 
113  mLabelJob.context.setRenderingStopped( true );
114  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
115  {
116  it->context.setRenderingStopped( true );
117  if ( it->renderer && it->renderer->feedback() )
118  it->renderer->feedback()->cancel();
119  }
120 
121  QTime t;
122  t.start();
123 
124  mFutureWatcher.waitForFinished();
125 
126  QgsDebugMsg( QString( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ) );
127 
128  futureFinished();
129 
130  QgsDebugMsg( "QPAINTER canceled" );
131 }
132 
134 {
135  if ( !isActive() )
136  return;
137 
138  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
139 
140  QTime t;
141  t.start();
142 
143  mFutureWatcher.waitForFinished();
144 
145  QgsDebugMsg( QString( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ) );
146 
147  futureFinished();
148 }
149 
151 {
152  return mActive;
153 }
154 
156 {
157  return mLabelJob.cached;
158 }
159 
161 {
162  if ( mLabelingEngineV2 )
163  return mLabelingEngineV2->takeResults();
164  else
165  return nullptr;
166 }
167 
168 
169 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
170 {
171  QEventLoop loop;
172  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
173  loop.exec( flags );
174 }
175 
176 
178 {
179  mRenderSynchronously = true;
180  start();
181  futureFinished();
182  mRenderSynchronously = false;
183 }
184 
185 
186 void QgsMapRendererCustomPainterJob::futureFinished()
187 {
188  mActive = false;
189  mRenderingTime = mRenderingStart.elapsed();
190  QgsDebugMsg( "QPAINTER futureFinished" );
191 
192  logRenderingTime( mLayerJobs, mLabelJob );
193 
194  // final cleanup
195  cleanupJobs( mLayerJobs );
196  cleanupLabelJob( mLabelJob );
197 
198  emit finished();
199 }
200 
201 
202 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob* self )
203 {
204  try
205  {
206  self->doRender();
207  }
208  catch ( QgsException & e )
209  {
210  Q_UNUSED( e );
211  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
212  }
213  catch ( std::exception & e )
214  {
215  Q_UNUSED( e );
216  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
217  }
218  catch ( ... )
219  {
220  QgsDebugMsg( "Caught unhandled unknown exception" );
221  }
222 }
223 
224 void QgsMapRendererCustomPainterJob::doRender()
225 {
226  QgsDebugMsg( "Starting to render layer stack." );
227  QTime renderTime;
228  renderTime.start();
229 
230  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
231  {
232  LayerRenderJob& job = *it;
233 
234  if ( job.context.renderingStopped() )
235  break;
236 
237  if ( job.context.useAdvancedEffects() )
238  {
239  // Set the QPainter composition mode so that this layer is rendered using
240  // the desired blending mode
241  mPainter->setCompositionMode( job.blendMode );
242  }
243 
244  if ( !job.cached )
245  {
246  QTime layerTime;
247  layerTime.start();
248 
249  if ( job.img )
250  job.img->fill( 0 );
251 
252  job.renderer->render();
253 
254  job.renderingTime = layerTime.elapsed();
255  }
256 
257  if ( job.img )
258  {
259  // If we flattened this layer for alternate blend modes, composite it now
260  mPainter->setOpacity( job.opacity );
261  mPainter->drawImage( 0, 0, *job.img );
262  mPainter->setOpacity( 1.0 );
263  }
264 
265  }
266 
267  QgsDebugMsg( "Done rendering map layers" );
268 
269  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
270  {
271  if ( !mLabelJob.cached )
272  {
273  QTime labelTime;
274  labelTime.start();
275 
276  if ( mLabelJob.img )
277  {
278  QPainter painter;
279  mLabelJob.img->fill( 0 );
280  painter.begin( mLabelJob.img );
281  mLabelJob.context.setPainter( &painter );
282  drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2.get(), &painter );
283  painter.end();
284  }
285  else
286  {
287  drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2.get(), mPainter );
288  }
289 
290  mLabelJob.complete = true;
291  mLabelJob.renderingTime = labelTime.elapsed();
292  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
293  }
294  }
295  if ( mLabelJob.img && mLabelJob.complete )
296  {
297  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
298  mPainter->setOpacity( 1.0 );
299  mPainter->drawImage( 0, 0, *mLabelJob.img );
300  }
301 
302  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
303 }
304 
305 
306 void QgsMapRendererJob::drawLabeling( const QgsMapSettings& settings, QgsRenderContext& renderContext, QgsLabelingEngine* labelingEngine2, QPainter* painter )
307 {
308  QgsDebugMsg( "Draw labeling start" );
309 
310  QTime t;
311  t.start();
312 
313  // Reset the composition mode before rendering the labels
314  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
315 
316  // TODO: this is not ideal - we could override rendering stopped flag that has been set in meanwhile
317  renderContext = QgsRenderContext::fromMapSettings( settings );
318  renderContext.setPainter( painter );
319 
320  if ( labelingEngine2 )
321  {
322  // set correct extent
323  renderContext.setExtent( settings.visibleExtent() );
325 
326  labelingEngine2->run( renderContext );
327  }
328 
329  QgsDebugMsg( QString( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ) );
330 }
331 
332 
333 void QgsMapRendererJob::updateLayerGeometryCaches()
334 {
335  QMap<QString, QgsGeometryCache>::const_iterator it = mGeometryCaches.constBegin();
336  for ( ; it != mGeometryCaches.constEnd(); ++it )
337  {
338  const QgsGeometryCache& cache = it.value();
339  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( it.key() ) ) )
340  * vl->cache() = cache;
341  }
342  mGeometryCaches.clear();
343 }
344 
345 
346 bool QgsMapRendererJob::needTemporaryImage( QgsMapLayer* ml )
347 {
348  if ( ml->type() == QgsMapLayer::VectorLayer )
349  {
350  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
351  if ( vl->renderer() && vl->renderer()->forceRasterRender() )
352  {
353  //raster rendering is forced for this layer
354  return true;
355  }
357  (( vl->blendMode() != QPainter::CompositionMode_SourceOver )
358  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
359  || ( vl->layerTransparency() != 0 ) ) )
360  {
361  //layer properties require rasterization
362  return true;
363  }
364  }
365  else if ( ml->type() == QgsMapLayer::RasterLayer )
366  {
367  // preview of intermediate raster rendering results requires a temporary output image
369  return true;
370  }
371 
372  return false;
373 }
374 
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:52
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:198
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...
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:355
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.