QGIS API Documentation  2.99.0-Master (6a61179)
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 "qgsmaplayerregistry.h"
22 #include "qgsmaplayerrenderer.h"
23 #include "qgspallabeling.h"
24 #include "qgsvectorlayer.h"
25 #include "qgsrenderer.h"
26 
27 
29  : QgsMapRendererJob( settings )
30  , mPainter( painter )
31  , mLabelingEngineV2( nullptr )
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  delete mLabelingEngineV2;
45  mLabelingEngineV2 = nullptr;
46 }
47 
49 {
50  if ( isActive() )
51  return;
52 
53  mRenderingStart.start();
54 
55  mActive = true;
56 
57  mErrors.clear();
58 
59  QgsDebugMsg( "QPAINTER run!" );
60 
61  QgsDebugMsg( "Preparing list of layer jobs for rendering" );
62  QTime prepareTime;
63  prepareTime.start();
64 
65  // clear the background
66  mPainter->fillRect( 0, 0, mSettings.outputSize().width(), mSettings.outputSize().height(), mSettings.backgroundColor() );
67 
68  mPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
69 
70 #ifndef QT_NO_DEBUG
71  QPaintDevice* thePaintDevice = mPainter->device();
72  QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" ).arg( thePaintDevice->logicalDpiX() ).arg( mSettings.outputDpi() );
73  Q_ASSERT_X( qgsDoubleNear( thePaintDevice->logicalDpiX(), mSettings.outputDpi() ), "Job::startRender()", errMsg.toLatin1().data() );
74 #endif
75 
76  delete mLabelingEngineV2;
77  mLabelingEngineV2 = nullptr;
78 
80  {
81  mLabelingEngineV2 = new QgsLabelingEngine();
82  mLabelingEngineV2->readSettingsFromProject();
83  mLabelingEngineV2->setMapSettings( mSettings );
84  }
85 
86  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2 );
87 
88  QgsDebugMsg( "Rendering prepared in (seconds): " + QString( "%1" ).arg( prepareTime.elapsed() / 1000.0 ) );
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, SIGNAL( finished() ), SLOT( futureFinished() ) );
99 
100  mFuture = QtConcurrent::run( staticRender, this );
101  mFutureWatcher.setFuture( mFuture );
102 }
103 
104 
106 {
107  if ( !isActive() )
108  {
109  QgsDebugMsg( "QPAINTER not running!" );
110  return;
111  }
112 
113  QgsDebugMsg( "QPAINTER cancelling" );
114  disconnect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( futureFinished() ) );
115 
116  mLabelingRenderContext.setRenderingStopped( true );
117  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
118  {
119  it->context.setRenderingStopped( true );
120  if ( it->renderer && it->renderer->feedback() )
121  it->renderer->feedback()->cancel();
122  }
123 
124  QTime t;
125  t.start();
126 
127  mFutureWatcher.waitForFinished();
128 
129  QgsDebugMsg( QString( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ) );
130 
131  futureFinished();
132 
133  QgsDebugMsg( "QPAINTER cancelled" );
134 }
135 
137 {
138  if ( !isActive() )
139  return;
140 
141  disconnect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( futureFinished() ) );
142 
143  QTime t;
144  t.start();
145 
146  mFutureWatcher.waitForFinished();
147 
148  QgsDebugMsg( QString( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ) );
149 
150  futureFinished();
151 }
152 
154 {
155  return mActive;
156 }
157 
158 
160 {
161  if ( mLabelingEngineV2 )
162  return mLabelingEngineV2->takeResults();
163  else
164  return nullptr;
165 }
166 
167 
168 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
169 {
170  QEventLoop loop;
171  connect( &mFutureWatcher, SIGNAL( finished() ), &loop, SLOT( quit() ) );
172  loop.exec( flags );
173 }
174 
175 
177 {
178  mRenderSynchronously = true;
179  start();
180  futureFinished();
181  mRenderSynchronously = false;
182 }
183 
184 
186 {
187  mActive = false;
188  mRenderingTime = mRenderingStart.elapsed();
189  QgsDebugMsg( "QPAINTER futureFinished" );
190 
191  logRenderingTime( mLayerJobs );
192 
193  // final cleanup
194  cleanupJobs( mLayerJobs );
195 
196  emit finished();
197 }
198 
199 
201 {
202  try
203  {
204  self->doRender();
205  }
206  catch ( QgsException & e )
207  {
208  Q_UNUSED( e );
209  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
210  }
211  catch ( std::exception & e )
212  {
213  Q_UNUSED( e );
214  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
215  }
216  catch ( ... )
217  {
218  QgsDebugMsg( "Caught unhandled unknown exception" );
219  }
220 }
221 
223 {
224  QgsDebugMsg( "Starting to render layer stack." );
225  QTime renderTime;
226  renderTime.start();
227 
228  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
229  {
230  LayerRenderJob& job = *it;
231 
232  if ( job.context.renderingStopped() )
233  break;
234 
235  if ( job.context.useAdvancedEffects() )
236  {
237  // Set the QPainter composition mode so that this layer is rendered using
238  // the desired blending mode
239  mPainter->setCompositionMode( job.blendMode );
240  }
241 
242  if ( !job.cached )
243  {
244  QTime layerTime;
245  layerTime.start();
246 
247  if ( job.img )
248  job.img->fill( 0 );
249 
250  job.renderer->render();
251 
252  job.renderingTime = layerTime.elapsed();
253  }
254 
255  if ( job.img )
256  {
257  // If we flattened this layer for alternate blend modes, composite it now
258  mPainter->setOpacity( job.opacity );
259  mPainter->drawImage( 0, 0, *job.img );
260  mPainter->setOpacity( 1.0 );
261  }
262 
263  }
264 
265  QgsDebugMsg( "Done rendering map layers" );
266 
267  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelingRenderContext.renderingStopped() )
268  drawLabeling( mSettings, mLabelingRenderContext, mLabelingEngineV2, mPainter );
269 
270  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
271 }
272 
273 
274 void QgsMapRendererJob::drawLabeling( const QgsMapSettings& settings, QgsRenderContext& renderContext, QgsLabelingEngine* labelingEngine2, QPainter* painter )
275 {
276  QgsDebugMsg( "Draw labeling start" );
277 
278  QTime t;
279  t.start();
280 
281  // Reset the composition mode before rendering the labels
282  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
283 
284  // TODO: this is not ideal - we could override rendering stopped flag that has been set in meanwhile
285  renderContext = QgsRenderContext::fromMapSettings( settings );
286  renderContext.setPainter( painter );
287 
288  if ( labelingEngine2 )
289  {
290  // set correct extent
291  renderContext.setExtent( settings.visibleExtent() );
293 
294  labelingEngine2->run( renderContext );
295  }
296 
297  QgsDebugMsg( QString( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ) );
298 }
299 
300 
302 {
303  QMap<QString, QgsGeometryCache>::const_iterator it = mGeometryCaches.constBegin();
304  for ( ; it != mGeometryCaches.constEnd(); ++it )
305  {
306  const QgsGeometryCache& cache = it.value();
307  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( it.key() ) ) )
308  * vl->cache() = cache;
309  }
310  mGeometryCaches.clear();
311 }
312 
313 
315 {
316  if ( ml->type() == QgsMapLayer::VectorLayer )
317  {
318  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
319  if ( vl->renderer() && vl->renderer()->forceRasterRender() )
320  {
321  //raster rendering is forced for this layer
322  return true;
323  }
325  (( vl->blendMode() != QPainter::CompositionMode_SourceOver )
326  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
327  || ( vl->layerTransparency() != 0 ) ) )
328  {
329  //layer properties require rasterisation
330  return true;
331  }
332  }
333  else if ( ml->type() == QgsMapLayer::RasterLayer )
334  {
335  // preview of intermediate raster rendering results requires a temporary output image
337  return true;
338  }
339 
340  return false;
341 }
342 
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void finished()
emitted when asynchronous rendering is finished (or canceled).
void setRenderingStopped(bool stopped)
Base class for all map layer types.
Definition: qgsmaplayer.h:49
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.
void updateLayerGeometryCaches()
called when rendering has finished to update all layers&#39; geometry caches
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
void logRenderingTime(const LayerRenderJobs &jobs)
static void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
QMap< QString, QgsGeometryCache > mGeometryCaches
map of geometry caches
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:333
bool renderingStopped() const
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:95
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:196
Enable drawing of labels on top of the map.
QString what() const
Definition: qgsexception.h:36
The QgsMapSettings class contains configuration for rendering of the map.
virtual void start() override
Start the rendering job and immediately return.
static void staticRender(QgsMapRendererCustomPainterJob *self)
Enable anti-aliasing for map rendering.
void setPainter(QPainter *p)
int layerTransparency() const
Returns the current transparency for the vector layer.
QgsFeatureRenderer * renderer()
Return renderer.
QPainter::CompositionMode featureBlendMode() const
Returns the current blending mode for features.
QgsMapSettings mSettings
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)
virtual bool render()=0
Do the rendering (based on data stored in the class)
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
int renderingTime
Time it took to render the layer in ms (it is -1 if not rendered or still rendering) ...
QgsLabelingResults * takeResults()
Return pointer to recently computed results and pass the ownership of results to the caller...
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
bool useAdvancedEffects() const
Returns true if advanced effects such as blend modes such be used.
QgsMapLayerRenderer * renderer
Class for doing transforms between two map coordinate systems.
QgsRenderContext context
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)
QPainter::CompositionMode blendMode
virtual void waitForFinished() override
Block until the job has finished.
Represents a vector layer which manages a vector based data sets.
bool needTemporaryImage(QgsMapLayer *ml)
Defines a qgis exception class.
Definition: qgsexception.h:25
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.
Structure keeping low-level rendering job information.
virtual bool isActive() const override
Tell whether the rendering job is currently running in background.