QGIS API Documentation  2.99.0-Master (40f86b2)
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 *paintDevice = mPainter->device();
68  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" ).arg( paintDevice->logicalDpiX() ).arg( mSettings.outputDpi() );
69  Q_ASSERT_X( qgsDoubleNear( paintDevice->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 );
113 
114  QTime t;
115  t.start();
116 
117  mFutureWatcher.waitForFinished();
118 
119  QgsDebugMsg( QString( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ) );
120 
121  futureFinished();
122 
123  QgsDebugMsg( "QPAINTER canceled" );
124 }
125 
127 {
128  if ( !isActive() )
129  {
130  QgsDebugMsg( "QPAINTER not running!" );
131  return;
132  }
133 
134  mLabelJob.context.setRenderingStopped( true );
135  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
136  {
137  it->context.setRenderingStopped( true );
138  if ( it->renderer && it->renderer->feedback() )
139  it->renderer->feedback()->cancel();
140  }
141 }
142 
144 {
145  if ( !isActive() )
146  return;
147 
148  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
149 
150  QTime t;
151  t.start();
152 
153  mFutureWatcher.waitForFinished();
154 
155  QgsDebugMsg( QString( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ) );
156 
157  futureFinished();
158 }
159 
161 {
162  return mActive;
163 }
164 
166 {
167  return mLabelJob.cached;
168 }
169 
171 {
172  if ( mLabelingEngineV2 )
173  return mLabelingEngineV2->takeResults();
174  else
175  return nullptr;
176 }
177 
178 
179 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
180 {
181  QEventLoop loop;
182  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
183  loop.exec( flags );
184 }
185 
186 
188 {
189  mRenderSynchronously = true;
190  start();
191  futureFinished();
192  mRenderSynchronously = false;
193 }
194 
195 
196 void QgsMapRendererCustomPainterJob::futureFinished()
197 {
198  mActive = false;
199  mRenderingTime = mRenderingStart.elapsed();
200  QgsDebugMsg( "QPAINTER futureFinished" );
201 
202  logRenderingTime( mLayerJobs, mLabelJob );
203 
204  // final cleanup
205  cleanupJobs( mLayerJobs );
206  cleanupLabelJob( mLabelJob );
207 
208  emit finished();
209 }
210 
211 
212 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
213 {
214  try
215  {
216  self->doRender();
217  }
218  catch ( QgsException &e )
219  {
220  Q_UNUSED( e );
221  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
222  }
223  catch ( std::exception &e )
224  {
225  Q_UNUSED( e );
226  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
227  }
228  catch ( ... )
229  {
230  QgsDebugMsg( "Caught unhandled unknown exception" );
231  }
232 }
233 
234 void QgsMapRendererCustomPainterJob::doRender()
235 {
236  QgsDebugMsg( "Starting to render layer stack." );
237  QTime renderTime;
238  renderTime.start();
239 
240  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
241  {
242  LayerRenderJob &job = *it;
243 
244  if ( job.context.renderingStopped() )
245  break;
246 
247  if ( job.context.useAdvancedEffects() )
248  {
249  // Set the QPainter composition mode so that this layer is rendered using
250  // the desired blending mode
251  mPainter->setCompositionMode( job.blendMode );
252  }
253 
254  if ( !job.cached )
255  {
256  QTime layerTime;
257  layerTime.start();
258 
259  if ( job.img )
260  job.img->fill( 0 );
261 
262  job.renderer->render();
263 
264  job.renderingTime = layerTime.elapsed();
265  }
266 
267  if ( job.img )
268  {
269  // If we flattened this layer for alternate blend modes, composite it now
270  mPainter->setOpacity( job.opacity );
271  mPainter->drawImage( 0, 0, *job.img );
272  mPainter->setOpacity( 1.0 );
273  }
274 
275  }
276 
277  QgsDebugMsg( "Done rendering map layers" );
278 
279  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
280  {
281  if ( !mLabelJob.cached )
282  {
283  QTime labelTime;
284  labelTime.start();
285 
286  if ( mLabelJob.img )
287  {
288  QPainter painter;
289  mLabelJob.img->fill( 0 );
290  painter.begin( mLabelJob.img );
291  mLabelJob.context.setPainter( &painter );
292  drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2.get(), &painter );
293  painter.end();
294  }
295  else
296  {
297  drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2.get(), mPainter );
298  }
299 
300  mLabelJob.complete = true;
301  mLabelJob.renderingTime = labelTime.elapsed();
302  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
303  }
304  }
305  if ( mLabelJob.img && mLabelJob.complete )
306  {
307  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
308  mPainter->setOpacity( 1.0 );
309  mPainter->drawImage( 0, 0, *mLabelJob.img );
310  }
311 
312  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
313 }
314 
315 
316 void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
317 {
318  QgsDebugMsg( "Draw labeling start" );
319 
320  QTime t;
321  t.start();
322 
323  // Reset the composition mode before rendering the labels
324  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
325 
326  // TODO: this is not ideal - we could override rendering stopped flag that has been set in meanwhile
327  renderContext = QgsRenderContext::fromMapSettings( settings );
328  renderContext.setPainter( painter );
329 
330  if ( labelingEngine2 )
331  {
332  // set correct extent
333  renderContext.setExtent( settings.visibleExtent() );
335 
336  labelingEngine2->run( renderContext );
337  }
338 
339  QgsDebugMsg( QString( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ) );
340 }
341 
342 
343 void QgsMapRendererJob::updateLayerGeometryCaches()
344 {
345  QMap<QString, QgsGeometryCache>::const_iterator it = mGeometryCaches.constBegin();
346  for ( ; it != mGeometryCaches.constEnd(); ++it )
347  {
348  const QgsGeometryCache &cache = it.value();
349  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( it.key() ) ) )
350  * vl->cache() = cache;
351  }
352  mGeometryCaches.clear();
353 }
354 
355 
356 bool QgsMapRendererJob::needTemporaryImage( QgsMapLayer *ml )
357 {
358  if ( ml->type() == QgsMapLayer::VectorLayer )
359  {
360  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
361  if ( vl->renderer() && vl->renderer()->forceRasterRender() )
362  {
363  //raster rendering is forced for this layer
364  return true;
365  }
367  ( ( vl->blendMode() != QPainter::CompositionMode_SourceOver )
368  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
369  || ( vl->layerTransparency() != 0 ) ) )
370  {
371  //layer properties require rasterization
372  return true;
373  }
374  }
375  else if ( ml->type() == QgsMapLayer::RasterLayer )
376  {
377  // preview of intermediate raster rendering results requires a temporary output image
379  return true;
380  }
381 
382  return false;
383 }
384 
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...
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:359
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.