QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
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 "qgsmaplayerrenderer.h"
22 #include "qgsmaplayerlistutils_p.h"
23 #include "qgselevationmap.h"
24 
25 #include <QtConcurrentRun>
26 
27 Q_GUI_EXPORT extern int qt_defaultDpiX();
28 Q_GUI_EXPORT extern int qt_defaultDpiY();
29 
30 static void _fixQPictureDPI( QPainter *p )
31 {
32  // QPicture makes an assumption that we drawing to it with system DPI.
33  // Then when being drawn, it scales the painter. The following call
34  // negates the effect. There is no way of setting QPicture's DPI.
35  // See QTBUG-20361
36  p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
37  static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
38 }
39 
40 //
41 // QgsMapRendererAbstractCustomPainterJob
42 //
43 
45  : QgsMapRendererJob( settings )
46 {
47 
48 }
49 
50 void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
51 {
52  // clear the background
53  painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
54 
55  painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing ) );
56  painter->setRenderHint( QPainter::SmoothPixmapTransform, mSettings.testFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms ) );
57  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
58 
59 #ifndef QT_NO_DEBUG
60  QPaintDevice *paintDevice = painter->device();
61  const QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
62  .arg( paintDevice->logicalDpiX() )
63  .arg( mSettings.outputDpi() );
64  Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi(), 1.0 ),
65  "Job::startRender()", errMsg.toLatin1().data() );
66 #endif
67 }
68 
69 
70 //
71 // QgsMapRendererCustomPainterJob
72 //
73 
76  , mPainter( painter )
77  , mActive( false )
78  , mRenderSynchronously( false )
79 {
80  QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
81 }
82 
84 {
85  QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
86  Q_ASSERT( !mFutureWatcher.isRunning() );
87  //cancel();
88 }
89 
90 void QgsMapRendererCustomPainterJob::startPrivate()
91 {
92  if ( isActive() )
93  return;
94 
95  if ( !mPrepareOnly )
96  mRenderingStart.start();
97 
98  mActive = true;
99 
100  mErrors.clear();
101 
102  QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
103 
104  QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
105  QElapsedTimer prepareTime;
106  prepareTime.start();
107 
109 
110  mLabelingEngineV2.reset();
111 
113  {
114  mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
115  mLabelingEngineV2->setMapSettings( mSettings );
116  }
117 
118  const bool canUseLabelCache = prepareLabelCache();
119  mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
120  mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
121  mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
122 
123  QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
124 
125  if ( mRenderSynchronously )
126  {
127  if ( !mPrepareOnly )
128  {
129  // do the rendering right now!
130  doRender();
131  }
132  return;
133  }
134 
135  // now we are ready to start rendering!
136  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
137 
138  mFuture = QtConcurrent::run( staticRender, this );
139  mFutureWatcher.setFuture( mFuture );
140 }
141 
142 
144 {
145  if ( !isActive() )
146  {
147  QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
148  return;
149  }
150 
151  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
152  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
154 
155  QElapsedTimer t;
156  t.start();
157 
158  mFutureWatcher.waitForFinished();
159 
160  QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
161 
162  futureFinished();
163 
164  QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
165 }
166 
168 {
169  if ( !isActive() )
170  {
171  QgsDebugError( QStringLiteral( "QPAINTER not running!" ) );
172  return;
173  }
174 
175  mLabelJob.context.setRenderingStopped( true );
176  for ( LayerRenderJob &job : mLayerJobs )
177  {
178  job.context()->setRenderingStopped( true );
179  if ( job.renderer && job.renderer->feedback() )
180  job.renderer->feedback()->cancel();
181  }
182 }
183 
185 {
186  if ( !isActive() )
187  return;
188 
189  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
190 
191  QElapsedTimer t;
192  t.start();
193 
194  mFutureWatcher.waitForFinished();
195 
196  QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
197 
198  futureFinished();
199 }
200 
202 {
203  return mActive;
204 }
205 
207 {
208  return mLabelJob.cached;
209 }
210 
212 {
213  if ( mLabelingEngineV2 )
214  return mLabelingEngineV2->takeResults();
215  else
216  return nullptr;
217 }
218 
219 
220 void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
221 {
222  QEventLoop loop;
223  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
224  loop.exec( flags );
225 }
226 
227 
229 {
230  mRenderSynchronously = true;
231  start();
232  futureFinished();
233  mRenderSynchronously = false;
234 }
235 
237 {
238  mRenderSynchronously = true;
239  mPrepareOnly = true;
240  start();
241  mPrepared = true;
242 }
243 
245 {
246  if ( !mPrepared )
247  return;
248 
249  doRender();
250  futureFinished();
251  mRenderSynchronously = false;
252  mPrepareOnly = false;
253  mPrepared = false;
254 }
255 
256 void QgsMapRendererCustomPainterJob::futureFinished()
257 {
258  mActive = false;
259  if ( !mPrepared ) // can't access from other thread
260  mRenderingTime = mRenderingStart.elapsed();
261  QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
262 
263  if ( !mPrepared )
264  logRenderingTime( mLayerJobs, {}, mLabelJob );
265 
266  // final cleanup
267  cleanupJobs( mLayerJobs );
268  cleanupSecondPassJobs( mSecondPassLayerJobs );
269  cleanupLabelJob( mLabelJob );
270 
271  emit finished();
272 }
273 
274 
275 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
276 {
277  try
278  {
279  self->doRender();
280  }
281  catch ( QgsException &e )
282  {
283  Q_UNUSED( e )
284  QgsDebugError( "Caught unhandled QgsException: " + e.what() );
285  }
286  catch ( std::exception &e )
287  {
288  Q_UNUSED( e )
289  QgsDebugError( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
290  }
291  catch ( ... )
292  {
293  QgsDebugError( QStringLiteral( "Caught unhandled unknown exception" ) );
294  }
295 }
296 
297 void QgsMapRendererCustomPainterJob::doRender()
298 {
299  const bool hasSecondPass = ! mSecondPassLayerJobs.empty();
300  QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
301  QElapsedTimer renderTime;
302  renderTime.start();
303 
305  std::unique_ptr<QgsElevationMap> mainElevationMap;
306  if ( mapShadingRenderer.isActive() )
307  mainElevationMap.reset( new QgsElevationMap( mSettings.deviceOutputSize(), mSettings.devicePixelRatio() ) );
308 
309  for ( LayerRenderJob &job : mLayerJobs )
310  {
311  if ( job.context()->renderingStopped() )
312  break;
313 
314  emit layerRenderingStarted( job.layerId );
315 
316  if ( ! hasSecondPass && job.context()->useAdvancedEffects() )
317  {
318  // Set the QPainter composition mode so that this layer is rendered using
319  // the desired blending mode
320  mPainter->setCompositionMode( job.blendMode );
321  }
322 
323  if ( !job.cached )
324  {
325  QElapsedTimer layerTime;
326  layerTime.start();
327 
328  if ( job.previewRenderImage && !job.previewRenderImageInitialized )
329  {
330  job.previewRenderImage->fill( 0 );
331  job.previewRenderImageInitialized = true;
332  }
333 
334  if ( job.img )
335  {
336  job.img->fill( 0 );
337  job.imageInitialized = true;
338  }
339 
340  job.completed = job.renderer->render();
341 
342  if ( job.picture )
343  {
344  job.renderer->renderContext()->painter()->end();
345  }
346 
347  job.renderingTime += layerTime.elapsed();
348  }
349 
350  if ( ! hasSecondPass && job.img )
351  {
352  // If we flattened this layer for alternate blend modes, composite it now
353  mPainter->setOpacity( job.opacity );
354  mPainter->drawImage( 0, 0, *job.img );
355  mPainter->setOpacity( 1.0 );
356  }
357 
358  if ( mainElevationMap && job.context()->elevationMap() )
359  {
360  const QgsElevationMap &layerElevationMap = *job.context()->elevationMap();
361  if ( layerElevationMap.isValid() )
362  mainElevationMap->combine( layerElevationMap, mapShadingRenderer.combinedElevationMethod() );
363  }
364 
365  emit layerRendered( job.layerId );
366  }
367 
369  QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
370 
371  if ( mapShadingRenderer.isActive() && mainElevationMap )
372  {
373  QImage image( mainElevationMap->rawElevationImage().size(), QImage::Format_RGB32 );
374  image.setDevicePixelRatio( mSettings.devicePixelRatio() );
375  image.fill( Qt::white );
376  mapShadingRenderer.renderShading( *mainElevationMap.get(), image, QgsRenderContext::fromMapSettings( mSettings ) );
377  mPainter->save();
378  mPainter->setCompositionMode( QPainter::CompositionMode_Multiply );
379  mPainter->drawImage( 0, 0, image );
380  mPainter->restore();
381  }
382 
383  if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
384  {
385  if ( !mLabelJob.cached )
386  {
387  QElapsedTimer labelTime;
388  labelTime.start();
389 
390  if ( mLabelJob.img )
391  {
392  QPainter painter;
393  mLabelJob.img->fill( 0 );
394  painter.begin( mLabelJob.img );
395  mLabelJob.context.setPainter( &painter );
396  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
397  painter.end();
398  }
399  else if ( mLabelJob.picture )
400  {
401  QPainter painter;
402  painter.begin( mLabelJob.picture.get() );
403  mLabelJob.context.setPainter( &painter );
404  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
405  painter.end();
406  }
407  else
408  {
409  drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
410  }
411 
412  mLabelJob.complete = true;
413  mLabelJob.renderingTime = labelTime.elapsed();
414  mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
415  }
416  }
417 
418  if ( ! hasSecondPass )
419  {
420  if ( mLabelJob.img && mLabelJob.complete )
421  {
422  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
423  mPainter->setOpacity( 1.0 );
424  mPainter->drawImage( 0, 0, *mLabelJob.img );
425  }
426  }
427  else
428  {
429  initSecondPassJobs( mSecondPassLayerJobs, mLabelJob );
430 
431  for ( LayerRenderJob &job : mSecondPassLayerJobs )
432  {
433  if ( job.context()->renderingStopped() )
434  break;
435 
436  if ( !job.cached )
437  {
438  QElapsedTimer layerTime;
439  layerTime.start();
440 
441  if ( job.previewRenderImage && !job.previewRenderImageInitialized )
442  {
443  job.previewRenderImage->fill( 0 );
444  job.previewRenderImageInitialized = true;
445  }
446 
447  if ( job.img )
448  {
449  job.img->fill( 0 );
450  job.imageInitialized = true;
451  }
452 
453  job.completed = job.renderer->render();
454 
455  if ( job.picture )
456  {
457  job.renderer->renderContext()->painter()->end();
458  }
459 
460  job.renderingTime += layerTime.elapsed();
461  }
462  }
463 
465  composeSecondPass( mSecondPassLayerJobs, mLabelJob, forceVector );
466 
467  if ( !forceVector )
468  {
469  const QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
470 
471  mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
472  mPainter->setOpacity( 1.0 );
473  mPainter->drawImage( 0, 0, finalImage );
474  }
475  else
476  {
477  //Vector composition is simply draw the saved picture on the painter
478  for ( LayerRenderJob &job : mLayerJobs )
479  {
480  // if there is vector rendering we use it, else we use the raster rendering
481  if ( job.picture )
482  {
483  mPainter->save();
484  _fixQPictureDPI( mPainter );
485  mPainter->drawPicture( 0, 0, *job.picture );
486  mPainter->restore();
487  }
488  else
489  mPainter->drawImage( 0, 0, *job.img );
490  }
491 
492  if ( mLabelJob.picture )
493  {
494  mPainter->save();
495  _fixQPictureDPI( mPainter );
496  mPainter->drawPicture( 0, 0, *mLabelJob.picture );
497  mPainter->restore();
498  }
499  }
500  }
501 
502  QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
503 }
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ ForceRasterMasks
Force symbol masking to be applied using a raster method. This is considerably faster when compared t...
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Enable anti-aliasing for map rendering.
@ DrawLabeling
Enable drawing of labels on top of the map.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
Stores digital elevation model in a raster image which may get updated as a part of map layer renderi...
void combine(const QgsElevationMap &otherElevationMap, Qgis::ElevationMapCombineMethod method)
Combines this elevation map with otherElevationMap.
bool isValid() const
Returns whether the elevation map is valid.
This class can render elevation shading on an image with different methods (eye dome lighting,...
Qgis::ElevationMapCombineMethod combinedElevationMethod() const
Returns the method used when conbining different elevation sources.
bool isActive() const
Returns whether this shading renderer is active.
void renderShading(const QgsElevationMap &elevation, QImage &image, const QgsRenderContext &context) const
Render shading on image condidering the elevation map elevation and the renderer context context If e...
Defines a QGIS exception class.
Definition: qgsexception.h:35
QString what() const
Definition: qgsexception.h:49
Class that stores computed placement from labeling engine.
Abstract base class for map renderer jobs which use custom painters.
void preparePainter(QPainter *painter, const QColor &backgroundColor=Qt::transparent)
Prepares the given painter ready for a map render.
QgsMapRendererAbstractCustomPainterJob(const QgsMapSettings &settings)
Constructor for QgsMapRendererAbstractCustomPainterJob, using the given map settings.
Job implementation that renders everything sequentially using a custom painter.
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void renderSynchronously()
Render the map synchronously in this thread.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
void waitForFinishedWithEventLoop(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Wait for the job to be finished - and keep the thread's event loop running while waiting.
void renderPrepared()
Render a pre-prepared job.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
void prepare()
Prepares the job for rendering synchronously in a background thread.
void waitForFinished() override
Block until the job has finished.
bool isActive() const override
Tell whether the rendering job is currently running in background.
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
Abstract base class for map rendering implementations.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
static QImage composeImage(const QgsMapSettings &settings, const std::vector< LayerRenderJob > &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
void cleanupSecondPassJobs(std::vector< LayerRenderJob > &jobs)
void initSecondPassJobs(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob) const
Initialize secondPassJobs according to what have been rendered (mask clipping path e....
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void layerRendered(const QString &layerId)
Emitted when a layer has completed rendering.
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void renderingLayersFinished()
Emitted when the layers are rendered.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
QElapsedTimer mRenderingStart
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapSettings mSettings
void start()
Start the rendering job and immediately return.
void layerRenderingStarted(const QString &layerId)
Emitted just before rendering starts for a particular layer.
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob, bool forceVector=false)
Compose second pass images into first pass images.
std::vector< LayerRenderJob > prepareSecondPassJobs(std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
The QgsMapSettings class contains configuration for rendering of the map.
QSize deviceOutputSize() const
Returns the device output size of the map render.
QColor backgroundColor() const
Returns the background color of the map.
float devicePixelRatio() const
Returns the device pixel ratio.
const QgsElevationShadingRenderer & elevationShadingRenderer() const
Returns the shading renderer used to render shading on the entire map.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5172
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38
Q_GUI_EXPORT int qt_defaultDpiX()
Q_GUI_EXPORT int qt_defaultDpiY()