QGIS API Documentation  2.99.0-Master (b698612)
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  QgsDebugMsg( "QPAINTER construct" );
36 }
37 
39 {
40  QgsDebugMsg( "QPAINTER destruct" );
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  QgsDebugMsg( "QPAINTER run!" );
57 
58  QgsDebugMsg( "Preparing list of layer jobs for rendering" );
59  QTime prepareTime;
60  prepareTime.start();
61 
62  // clear the background
63  mPainter->fillRect( 0, 0, mSettings.outputSize().width(), mSettings.outputSize().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)" ).arg( paintDevice->logicalDpiX() ).arg( mSettings.outputDpi() );
70  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() ), "Job::startRender()", errMsg.toLatin1().data() );
71 #endif
72 
73  mLabelingEngineV2.reset();
74 
76  {
77  mLabelingEngineV2.reset( new QgsLabelingEngine() );
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  {
261  job.img->fill( 0 );
262  job.imageInitialized = true;
263  }
264 
265  job.renderer->render();
266 
267  job.renderingTime = layerTime.elapsed();
268  }
269 
270  if ( job.img )
271  {
272  // If we flattened this layer for alternate blend modes, composite it now
273  mPainter->setOpacity( job.opacity );
274  mPainter->drawImage( 0, 0, *job.img );
275  mPainter->setOpacity( 1.0 );
276  }
277 
278  }
279 
280  QgsDebugMsg( "Done rendering map layers" );
281 
282  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
283  {
284  if ( !mLabelJob.cached )
285  {
286  QTime labelTime;
287  labelTime.start();
288 
289  if ( mLabelJob.img )
290  {
291  QPainter painter;
292  mLabelJob.img->fill( 0 );
293  painter.begin( mLabelJob.img );
294  mLabelJob.context.setPainter( &painter );
295  drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2.get(), &painter );
296  painter.end();
297  }
298  else
299  {
300  drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2.get(), mPainter );
301  }
302 
303  mLabelJob.complete = true;
304  mLabelJob.renderingTime = labelTime.elapsed();
305  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
306  }
307  }
308  if ( mLabelJob.img && mLabelJob.complete )
309  {
310  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
311  mPainter->setOpacity( 1.0 );
312  mPainter->drawImage( 0, 0, *mLabelJob.img );
313  }
314 
315  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
316 }
317 
318 
319 void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
320 {
321  QgsDebugMsg( "Draw labeling start" );
322 
323  QTime t;
324  t.start();
325 
326  // Reset the composition mode before rendering the labels
327  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
328 
329  // TODO: this is not ideal - we could override rendering stopped flag that has been set in meanwhile
330  renderContext = QgsRenderContext::fromMapSettings( settings );
331  renderContext.setPainter( painter );
332 
333  if ( labelingEngine2 )
334  {
335  // set correct extent
336  renderContext.setExtent( settings.visibleExtent() );
338 
339  labelingEngine2->run( renderContext );
340  }
341 
342  QgsDebugMsg( QString( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ) );
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  || ( !qgsDoubleNear( vl->opacity(), 1.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:54
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:37
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:357
virtual void cancel() override
Stop the rendering job - does not return until the job has terminated.
Enable layer opacity and blending effects.
QgsMapLayer::LayerType type() const
Returns the type of the layer.
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:203
Enable drawing of labels on top of the map.
QString what() const
Definition: qgsexception.h:42
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.
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
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:31
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.