QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsmaprendererjob.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaprendererjob.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 
16 #include "qgsmaprendererjob.h"
17 
18 #include <QPainter>
19 #include <QElapsedTimer>
20 #include <QTimer>
21 #include <QtConcurrentMap>
22 
23 #include "qgslogger.h"
24 #include "qgsrendercontext.h"
25 #include "qgsmaplayer.h"
26 #include "qgsproject.h"
27 #include "qgsmaplayerrenderer.h"
29 #include "qgsmaprenderercache.h"
30 #include "qgsmessagelog.h"
31 #include "qgspallabeling.h"
32 #include "qgsexception.h"
33 #include "qgslabelingengine.h"
34 #include "qgsmaplayerlistutils.h"
35 #include "qgsvectorlayerlabeling.h"
36 #include "qgssettings.h"
38 #include "qgssymbol.h"
39 #include "qgsrenderer.h"
40 #include "qgssymbollayer.h"
41 #include "qgsvectorlayerutils.h"
42 #include "qgssymbollayerutils.h"
45 #include "qgsvectorlayerrenderer.h"
46 
48 
49 const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
50 const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
51 
52 bool LayerRenderJob::imageCanBeComposed() const
53 {
54  if ( imageInitialized )
55  {
56  if ( renderer )
57  {
58  return renderer->isReadyToCompose();
59  }
60  else
61  {
62  return true;
63  }
64  }
65  else
66  {
67  return false;
68  }
69 }
70 
72  : mSettings( settings )
73 {}
74 
76 {
78  startPrivate();
79  else
80  {
81  mErrors.append( QgsMapRendererJob::Error( QString(), tr( "Invalid map settings" ) ) );
82  emit finished();
83  }
84 }
85 
87  : QgsMapRendererJob( settings )
88 {
89 }
90 
91 
93 {
94  return mErrors;
95 }
96 
98 {
99  mCache = cache;
100 }
101 
102 QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
103 {
104  QHash<QgsMapLayer *, int> result;
105  for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
106  {
107  if ( auto &&lKey = it.key() )
108  result.insert( lKey, it.value() );
109  }
110  return result;
111 }
112 
113 void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
114 {
115  mLayerRenderingTimeHints = hints;
116 }
117 
119 {
120  return mSettings;
121 }
122 
124 {
125  bool canCache = mCache;
126 
127  // calculate which layers will be labeled
128  QSet< QgsMapLayer * > labeledLayers;
129  const QList<QgsMapLayer *> layers = mSettings.layers();
130  for ( QgsMapLayer *ml : layers )
131  {
133  labeledLayers << ml;
134 
135  switch ( ml->type() )
136  {
138  {
139  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
140  if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
141  {
142  canCache = false;
143  }
144  break;
145  }
146 
148  {
149  // TODO -- add detection of advanced labeling effects for vector tile layers
150  break;
151  }
152 
158  break;
159  }
160 
161  if ( !canCache )
162  break;
163 
164  }
165 
167  {
168  // we may need to clear label cache and re-register labeled features - check for that here
169 
170  // can we reuse the cached label solution?
171  bool canUseCache = canCache && qgis::listToSet( mCache->dependentLayers( LABEL_CACHE_ID ) ) == labeledLayers;
172  if ( !canUseCache )
173  {
174  // no - participating layers have changed
176  }
177  }
178  return canCache;
179 }
180 
181 
182 bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
183 {
184  bool res = true;
185  // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
186  // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
187  QgsCoordinateTransform approxTransform = ct;
188  approxTransform.setBallparkTransformsAreAppropriate( true );
189 
190  try
191  {
192 #ifdef QGISDEBUG
193  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
194 #endif
195  // Split the extent into two if the source CRS is
196  // geographic and the extent crosses the split in
197  // geographic coordinates (usually +/- 180 degrees,
198  // and is assumed to be so here), and draw each
199  // extent separately.
200  static const double SPLIT_COORD = 180.0;
201 
202  if ( ml->crs().isGeographic() )
203  {
204  if ( ml->type() == QgsMapLayerType::VectorLayer && !approxTransform.destinationCrs().isGeographic() )
205  {
206  // if we transform from a projected coordinate system check
207  // check if transforming back roughly returns the input
208  // extend - otherwise render the world.
210  QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, QgsCoordinateTransform::ForwardTransform );
211 
212  QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
213  .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
214  .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
215  .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
216  .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
217  , 3 );
218 
219  // can differ by a maximum of up to 20% of height/width
220  if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
221  && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
222  && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
223  && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
224  )
225  {
226  extent = extent1;
227  }
228  else
229  {
230  extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
231  res = false;
232  }
233  }
234  else
235  {
236  // Note: ll = lower left point
237  QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
239 
240  // and ur = upper right point
241  QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
243 
244  QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
245 
246  extent = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
247 
248  QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
249 
250  if ( ll.x() > ur.x() )
251  {
252  // the coordinates projected in reverse order than what one would expect.
253  // we are probably looking at an area that includes longitude of 180 degrees.
254  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
255  // so let's use (-180,180). This hopefully does not add too much overhead. It is
256  // more straightforward than rendering with two separate extents and more consistent
257  // for rendering, labeling and caching as everything is rendered just in one go
258  extent.setXMinimum( -SPLIT_COORD );
259  extent.setXMaximum( SPLIT_COORD );
260  res = false;
261  }
262  }
263 
264  // TODO: the above rule still does not help if using a projection that covers the whole
265  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
266  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
267  // but in fact the extent should cover the whole world.
268  }
269  else // can't cross 180
270  {
271  if ( approxTransform.destinationCrs().isGeographic() &&
272  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
273  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
274  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
275  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
276  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
277  // but this seems like a safer choice.
278  {
279  extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
280  res = false;
281  }
282  else
283  extent = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
284  }
285  }
286  catch ( QgsCsException & )
287  {
288  QgsDebugMsg( QStringLiteral( "Transform error caught" ) );
289  extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
290  r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
291  res = false;
292  }
293 
294  return res;
295 }
296 
297 QImage *QgsMapRendererJob::allocateImage( QString layerId )
298 {
299  QImage *image = new QImage( mSettings.deviceOutputSize(),
301  image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
302  if ( image->isNull() )
303  {
304  mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
305  delete image;
306  return nullptr;
307  }
308  return image;
309 }
310 
311 QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image )
312 {
313  QPainter *painter = nullptr;
314  image = allocateImage( layerId );
315  if ( image )
316  {
317  painter = new QPainter( image );
318  painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
319 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
320  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
321 #endif
322  }
323  return painter;
324 }
325 
326 LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
327 {
328  LayerRenderJobs layerJobs;
329 
330  // render all layers in the stack, starting at the base
331  QListIterator<QgsMapLayer *> li( mSettings.layers() );
332  li.toBack();
333 
334  if ( mCache )
335  {
337  Q_UNUSED( cacheValid )
338  QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
339  }
340 
341  bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
342 
343  while ( li.hasPrevious() )
344  {
345  QgsMapLayer *ml = li.previous();
346 
347  QgsDebugMsgLevel( QStringLiteral( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
348  .arg( ml->name() )
349  .arg( ml->minimumScale() )
350  .arg( ml->maximumScale() )
351  .arg( ml->hasScaleBasedVisibility() )
352  .arg( ml->blendMode() )
353  .arg( ml->isValid() )
354  , 3 );
355 
356  if ( !ml->isValid() )
357  {
358  QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
359  continue;
360  }
361 
362  if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
363  {
364  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
365  continue;
366  }
367 
369  {
370  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
371  continue;
372  }
373 
375  {
376  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
377  continue;
378  }
379 
381  r1.grow( mSettings.extentBuffer() );
383 
384  ct = mSettings.layerTransform( ml );
385  bool haveExtentInLayerCrs = true;
386  if ( ct.isValid() )
387  {
388  haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
389  }
390  QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
391  if ( !r1.isFinite() || !r2.isFinite() )
392  {
393  mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
394  continue;
395  }
396 
397  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
398 
399  // Force render of layers that are being edited
400  // or if there's a labeling engine that needs the layer to register features
401  if ( mCache )
402  {
403  const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
404  if ( ( vl && vl->isEditable() ) || requiresLabeling )
405  {
406  mCache->clearCacheImage( ml->id() );
407  }
408  }
409 
410  layerJobs.append( LayerRenderJob() );
411  LayerRenderJob &job = layerJobs.last();
412  job.cached = false;
413  job.img = nullptr;
414  job.layer = ml;
415  job.layerId = ml->id();
416  job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
417  job.renderingTime = -1;
418 
419 
421  job.context.expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
422  job.context.setPainter( painter );
423  job.context.setLabelingEngine( labelingEngine2 );
424  job.context.setCoordinateTransform( ct );
425  job.context.setExtent( r1 );
426  if ( !haveExtentInLayerCrs )
427  job.context.setFlag( QgsRenderContext::ApplyClipAfterReprojection, true );
428 
429  if ( mFeatureFilterProvider )
430  job.context.setFeatureFilterProvider( mFeatureFilterProvider );
431 
432  QgsMapLayerStyleOverride styleOverride( ml );
433  if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
434  styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
435 
436  job.blendMode = ml->blendMode();
437 
438  // raster layer opacity is handled directly within the raster layer renderer, so don't
439  // apply default opacity handling here!
440  job.opacity = ml->type() != QgsMapLayerType::RasterLayer ? ml->opacity() : 1.0;
441 
442  // if we can use the cache, let's do it and avoid rendering!
443  if ( mCache && mCache->hasCacheImage( ml->id() ) )
444  {
445  job.cached = true;
446  job.imageInitialized = true;
447  job.img = new QImage( mCache->cacheImage( ml->id() ) );
448  job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
449  job.renderer = nullptr;
450  job.context.setPainter( nullptr );
451  continue;
452  }
453 
454  QElapsedTimer layerTime;
455  layerTime.start();
456  job.renderer = ml->createMapRenderer( job.context );
457  if ( job.renderer )
458  job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
459 
460  // If we are drawing with an alternative blending mode then we need to render to a separate image
461  // before compositing this on the map. This effectively flattens the layer and prevents
462  // blending occurring between objects on the layer
463  if ( mCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
464  {
465  // Flattened image for drawing when a blending mode is set
466  job.context.setPainter( allocateImageAndPainter( ml->id(), job.img ) );
467  if ( ! job.img )
468  {
469  delete job.renderer;
470  job.renderer = nullptr;
471  layerJobs.removeLast();
472  continue;
473  }
474  }
475 
476  job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
477  }
478 
479  return layerJobs;
480 }
481 
482 LayerRenderJobs QgsMapRendererJob::prepareSecondPassJobs( LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob )
483 {
484  LayerRenderJobs secondPassJobs;
485 
486  // We will need to quickly access the associated rendering job of a layer
487  QHash<QString, LayerRenderJob *> layerJobMapping;
488 
489  // ... and whether a layer has a mask defined
490  QSet<QString> layerHasMask;
491 
492  struct MaskSource
493  {
494  QString layerId;
495  QString labelRuleId;
496  int labelMaskId;
497  MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_ ):
498  layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ) {}
499  };
500 
501  // We collect for each layer, the set of symbol layers that will be "masked"
502  // and the list of source layers that have a mask
503  QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
504 
505  // First up, create a mapping of layer id to jobs. We need this to filter out any masking
506  // which refers to layers which we aren't rendering as part of this map render
507  for ( LayerRenderJob &job : firstPassJobs )
508  {
509  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
510  if ( ! vl )
511  continue;
512 
513  layerJobMapping[job.layer->id()] = &job;
514  }
515 
516  // next, collate a master list of masked layers, skipping over any which refer to layers
517  // which don't have a corresponding render job
518  for ( LayerRenderJob &job : firstPassJobs )
519  {
520  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
521  if ( ! vl )
522  continue;
523 
524  // lambda function to factor code for both label masks and symbol layer masks
525  auto collectMasks = [&]( QHash<QString, QSet<QgsSymbolLayerId>> *masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
526  {
527  for ( auto it = masks->begin(); it != masks->end(); ++it )
528  {
529  auto lit = maskedSymbolLayers.find( it.key() );
530  if ( lit == maskedSymbolLayers.end() )
531  {
532  maskedSymbolLayers[it.key()] = qMakePair( it.value(), QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId ) );
533  }
534  else
535  {
536  if ( lit->first != it.value() )
537  {
538  QgsLogger::warning( QStringLiteral( "Layer %1 : Different sets of symbol layers are masked by different sources ! Only one (arbitrary) set will be retained !" ).arg( it.key() ) );
539  continue;
540  }
541  lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId ) );
542  }
543  }
544  if ( ! masks->isEmpty() )
545  layerHasMask.insert( sourceLayerId );
546  };
547 
548  // collect label masks
549  QHash<QString, QHash<QString, QSet<QgsSymbolLayerId>>> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
550  for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
551  {
552  QString labelRule = it.key();
553  // this is a hash of layer id to masks
554  QHash<QString, QSet<QgsSymbolLayerId>> masks = it.value();
555 
556  // filter out masks to those which we are actually rendering
557  QHash<QString, QSet<QgsSymbolLayerId>> usableMasks;
558  for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
559  {
560  const QString sourceLayerId = mit.key();
561  // if we aren't rendering the source layer as part of this render, we can't process this mask
562  if ( !layerJobMapping.contains( sourceLayerId ) )
563  continue;
564  else
565  usableMasks.insert( sourceLayerId, mit.value() );
566  }
567 
568  if ( usableMasks.empty() )
569  continue;
570 
571  // group layers by QSet<QgsSymbolLayerReference>
572  QSet<QgsSymbolLayerReference> slRefs;
573  for ( auto mit = usableMasks.begin(); mit != usableMasks.end(); mit++ )
574  {
575  const QString sourceLayerId = mit.key();
576  // if we aren't rendering the source layer as part of this render, we can't process this mask
577  if ( !layerJobMapping.contains( sourceLayerId ) )
578  continue;
579 
580  for ( auto slIt = mit.value().begin(); slIt != mit.value().end(); slIt++ )
581  {
582  slRefs.insert( QgsSymbolLayerReference( mit.key(), *slIt ) );
583  }
584  }
585  // generate a new mask id for this set
586  int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
587 
588  // now collect masks
589  collectMasks( &usableMasks, vl->id(), labelRule, labelMaskId );
590  }
591 
592  // collect symbol layer masks
593  QHash<QString, QSet<QgsSymbolLayerId>> symbolLayerMasks = QgsVectorLayerUtils::symbolLayerMasks( vl );
594  collectMasks( &symbolLayerMasks, vl->id() );
595  }
596 
597  if ( maskedSymbolLayers.isEmpty() )
598  return secondPassJobs;
599 
600  // Now that we know some layers have a mask, we have to allocate a mask image and painter
601  // for them in the first pass job
602  for ( LayerRenderJob &job : firstPassJobs )
603  {
604  QgsMapLayer *ml = job.layer;
605 
606  if ( job.img == nullptr )
607  {
608  job.context.setPainter( allocateImageAndPainter( ml->id(), job.img ) );
609  }
610  if ( layerHasMask.contains( ml->id() ) )
611  {
612  // Note: we only need an alpha channel here, rather than a full RGBA image
613  job.context.setMaskPainter( allocateImageAndPainter( ml->id(), job.maskImage ) );
614  job.maskImage->fill( 0 );
615  }
616  }
617 
618  // Allocate an image for labels
619  if ( labelJob.img == nullptr )
620  {
621  labelJob.img = allocateImage( QStringLiteral( "labels" ) );
622  }
623 
624  // Prepare label mask images
625  for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
626  {
627  QImage *maskImage;
628  labelJob.context.setMaskPainter( allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage ), maskId );
629  maskImage->fill( 0 );
630  labelJob.maskImages.push_back( maskImage );
631  }
632  labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
633 
634  // Prepare second pass jobs
635  for ( LayerRenderJob &job : firstPassJobs )
636  {
637  QgsMapLayer *ml = job.layer;
638 
639  auto it = maskedSymbolLayers.find( ml->id() );
640  if ( it == maskedSymbolLayers.end() )
641  continue;
642 
643  QList<MaskSource> &sourceList = it->second;
644  const QSet<QgsSymbolLayerId> &symbolList = it->first;
645 
646  // copy the initial job ...
647  secondPassJobs.append( LayerRenderJob() );
648  LayerRenderJob &job2 = secondPassJobs.last();
649  job2 = job;
650  job2.cached = false;
651  job2.firstPassJob = &job;
652  QgsVectorLayer *vl1 = qobject_cast<QgsVectorLayer *>( job.layer );
653 
654  // ... but clear the image
655  job2.context.setMaskPainter( nullptr );
656  job2.context.setPainter( allocateImageAndPainter( vl1->id(), job2.img ) );
657  if ( ! job2.img )
658  {
659  secondPassJobs.removeLast();
660  continue;
661  }
662 
663  // Points to the first pass job. This will be needed during the second pass composition.
664  for ( MaskSource &source : sourceList )
665  {
666  if ( source.labelMaskId != -1 )
667  job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
668  else
669  job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
670  }
671 
672  // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
673  // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
674  QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( vl1->createMapRenderer( job2.context ) );
675  job2.renderer = mapRenderer;
676 
677  // Modify the render context so that symbol layers get disabled as needed.
678  // The map renderer stores a reference to the context, so we can modify it even after the map renderer creation (what we need here)
679  job2.context.setDisabledSymbolLayers( QgsSymbolLayerUtils::toSymbolLayerPointers( mapRenderer->featureRenderer(), symbolList ) );
680  }
681 
682  return secondPassJobs;
683 }
684 
685 LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
686 {
687  LabelRenderJob job;
689  job.context.setPainter( painter );
690  job.context.setLabelingEngine( labelingEngine2 );
691  job.context.setExtent( mSettings.visibleExtent() );
692  job.context.setFeatureFilterProvider( mFeatureFilterProvider );
695  job.context.setCoordinateTransform( ct );
696 
697  // if we can use the cache, let's do it and avoid rendering!
698  bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
699  if ( hasCache )
700  {
701  job.cached = true;
702  job.complete = true;
703  job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
704  Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
705  job.context.setPainter( nullptr );
706  }
707  else
708  {
709  if ( canUseLabelCache && ( mCache || !painter ) )
710  {
711  job.img = allocateImage( QStringLiteral( "labels" ) );
712  }
713  }
714 
715  return job;
716 }
717 
718 
719 void QgsMapRendererJob::cleanupJobs( LayerRenderJobs &jobs )
720 {
721  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
722  {
723  LayerRenderJob &job = *it;
724  if ( job.img )
725  {
726  delete job.context.painter();
727  job.context.setPainter( nullptr );
728 
729  if ( mCache && !job.cached && job.completed && job.layer )
730  {
731  QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
732  mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
733  mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
734  }
735 
736  delete job.img;
737  job.img = nullptr;
738  }
739 
740  // delete the mask image and painter
741  if ( job.maskImage )
742  {
743  delete job.context.maskPainter();
744  job.context.setMaskPainter( nullptr );
745  delete job.maskImage;
746  }
747 
748  if ( job.renderer )
749  {
750  const auto constErrors = job.renderer->errors();
751  for ( const QString &message : constErrors )
752  mErrors.append( Error( job.renderer->layerId(), message ) );
753 
754  delete job.renderer;
755  job.renderer = nullptr;
756  }
757 
758  if ( job.layer )
759  mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
760  }
761 
762  jobs.clear();
763 }
764 
765 void QgsMapRendererJob::cleanupSecondPassJobs( LayerRenderJobs &jobs )
766 {
767  for ( auto &job : jobs )
768  {
769  if ( job.img )
770  {
771  delete job.context.painter();
772  job.context.setPainter( nullptr );
773 
774  delete job.img;
775  job.img = nullptr;
776  }
777 
778  if ( job.renderer )
779  {
780  delete job.renderer;
781  job.renderer = nullptr;
782  }
783 
784  if ( job.layer )
785  mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
786  }
787 
788  jobs.clear();
789 }
790 
791 void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
792 {
793  if ( job.img )
794  {
795  if ( mCache && !job.cached && !job.context.renderingStopped() )
796  {
797  QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
798  mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
799  mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
800  }
801 
802  delete job.img;
803  job.img = nullptr;
804  }
805 
806  for ( int maskId = 0; maskId < job.maskImages.size(); maskId++ )
807  {
808  delete job.context.maskPainter( maskId );
809  job.context.setMaskPainter( nullptr, maskId );
810  delete job.maskImages[maskId];
811  }
812 }
813 
814 
815 #define DEBUG_RENDERING 0
816 
818  const QgsMapSettings &settings,
819  const LayerRenderJobs &jobs,
820  const LabelRenderJob &labelJob,
821  const QgsMapRendererCache *cache
822 )
823 {
824  QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
825  image.setDevicePixelRatio( settings.devicePixelRatio() );
826  image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
827  image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
828  image.fill( settings.backgroundColor().rgba() );
829 
830  QPainter painter( &image );
831 
832 #if DEBUG_RENDERING
833  int i = 0;
834 #endif
835  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
836  {
837  const LayerRenderJob &job = *it;
838 
839  if ( job.layer && job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
840  continue; // skip layer for now, it will be rendered after labels
841 
842  QImage img = layerImageToBeComposed( settings, job, cache );
843  if ( img.isNull() )
844  continue; // image is not prepared and not even in cache
845 
846  painter.setCompositionMode( job.blendMode );
847  painter.setOpacity( job.opacity );
848 
849 #if DEBUG_RENDERING
850  img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
851  i++;
852 #endif
853 
854  painter.drawImage( 0, 0, img );
855  }
856 
857  // IMPORTANT - don't draw labelJob img before the label job is complete,
858  // as the image is uninitialized and full of garbage before the label job
859  // commences
860  if ( labelJob.img && labelJob.complete )
861  {
862  painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
863  painter.setOpacity( 1.0 );
864  painter.drawImage( 0, 0, *labelJob.img );
865  }
866  // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
867  // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
868  // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
869  else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
870  {
871  const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
872  painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
873  painter.setOpacity( 1.0 );
874  painter.drawImage( 0, 0, labelCacheImage );
875  }
876 
877  // render any layers with the renderAboveLabels flag now
878  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
879  {
880  const LayerRenderJob &job = *it;
881 
882  if ( !job.layer || !job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
883  continue;
884 
885  QImage img = layerImageToBeComposed( settings, job, cache );
886  if ( img.isNull() )
887  continue; // image is not prepared and not even in cache
888 
889  painter.setCompositionMode( job.blendMode );
890  painter.setOpacity( job.opacity );
891 
892  painter.drawImage( 0, 0, img );
893  }
894 
895  painter.end();
896 #if DEBUG_RENDERING
897  image.save( "/tmp/final.png" );
898 #endif
899  return image;
900 }
901 
903  const QgsMapSettings &settings,
904  const LayerRenderJob &job,
905  const QgsMapRendererCache *cache
906 )
907 {
908  if ( job.imageCanBeComposed() )
909  {
910  Q_ASSERT( job.img );
911  return *job.img;
912  }
913  else
914  {
915  if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
916  {
917  return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
918  }
919  else
920  return QImage();
921  }
922 }
923 
924 void QgsMapRendererJob::composeSecondPass( LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob )
925 {
926 #if DEBUG_RENDERING
927  int i = 0;
928 #endif
929  // compose the second pass with the mask
930  for ( LayerRenderJob &job : secondPassJobs )
931  {
932 #if DEBUG_RENDERING
933  i++;
934  job.img->save( QString( "/tmp/second_%1.png" ).arg( i ) );
935  int mask = 0;
936 #endif
937 
938  // Merge all mask images into the first one if we have more than one mask image
939  if ( job.maskJobs.size() > 1 )
940  {
941  QPainter *maskPainter = nullptr;
942  for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
943  {
944  QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
945 #if DEBUG_RENDERING
946  maskImage->save( QString( "/tmp/mask_%1_%2.png" ).arg( i ).arg( mask++ ) );
947 #endif
948  if ( ! maskPainter )
949  {
950  maskPainter = p.first ? p.first->context.maskPainter() : labelJob.context.maskPainter( p.second );
951  }
952  else
953  {
954  maskPainter->drawImage( 0, 0, *maskImage );
955  }
956  }
957  }
958 
959  if ( ! job.maskJobs.isEmpty() )
960  {
961  // All have been merged into the first
962  QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
963  QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
964 #if DEBUG_RENDERING
965  maskImage->save( QString( "/tmp/mask_%1.png" ).arg( i ) );
966 #endif
967 
968  // Only retain parts of the second rendering that are "inside" the mask image
969  QPainter *painter = job.context.painter();
970  painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
971 
972  //Create an "alpha binarized" image of the maskImage to :
973  //* Eliminate antialiasing artifact
974  //* Avoid applying mask opacity to elements under the mask but not masked
975  QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
976  QVector<QRgb> mswTable;
977  mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
978  mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
979  maskBinAlpha.setColorTable( mswTable );
980  painter->drawImage( 0, 0, maskBinAlpha );
981 #if DEBUG_RENDERING
982  job.img->save( QString( "/tmp/second_%1_a.png" ).arg( i ) );
983 #endif
984 
985  // Modify the first pass' image ...
986  {
987  QPainter tempPainter;
988 
989  // reuse the first pass painter, if available
990  QPainter *painter1 = job.firstPassJob->context.painter();
991  if ( ! painter1 )
992  {
993  tempPainter.begin( job.firstPassJob->img );
994  painter1 = &tempPainter;
995  }
996 #if DEBUG_RENDERING
997  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_1.png" ).arg( i ) );
998 #endif
999  // ... first retain parts that are "outside" the mask image
1000  painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
1001  painter1->drawImage( 0, 0, *maskImage );
1002 
1003 #if DEBUG_RENDERING
1004  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_2.png" ).arg( i ) );
1005 #endif
1006  // ... and overpaint the second pass' image on it
1007  painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
1008  painter1->drawImage( 0, 0, *job.img );
1009 #if DEBUG_RENDERING
1010  job.img->save( QString( "/tmp/second_%1_b.png" ).arg( i ) );
1011  if ( job.firstPassJob )
1012  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_3.png" ).arg( i ) );
1013 #endif
1014  }
1015  }
1016  }
1017 }
1018 
1019 void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob )
1020 {
1021  if ( !settingsLogCanvasRefreshEvent.value() )
1022  return;
1023 
1024  QMultiMap<int, QString> elapsed;
1025  const auto constJobs = jobs;
1026  for ( const LayerRenderJob &job : constJobs )
1027  elapsed.insert( job.renderingTime, job.layerId );
1028  const auto constSecondPassJobs = secondPassJobs;
1029  for ( const LayerRenderJob &job : constSecondPassJobs )
1030  elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
1031 
1032  elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
1033 
1034  QList<int> tt( elapsed.uniqueKeys() );
1035  std::sort( tt.begin(), tt.end(), std::greater<int>() );
1036  const auto constTt = tt;
1037  for ( int t : constTt )
1038  {
1039  QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
1040  }
1041  QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
1042 }
1043 
1044 void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1045 {
1046  QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1047 
1048  QElapsedTimer t;
1049  t.start();
1050 
1051  // Reset the composition mode before rendering the labels
1052  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1053 
1054  renderContext.setPainter( painter );
1055 
1056  if ( labelingEngine2 )
1057  {
1058  labelingEngine2->run( renderContext );
1059  }
1060 
1061  QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1062 }
1063 
1064 void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1065 {
1066  Q_UNUSED( settings )
1067 
1068  drawLabeling( renderContext, labelingEngine2, painter );
1069 }
1070 
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
Class for doing transforms between two map coordinate systems.
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
@ ForwardTransform
Transform from source to destination CRS.
@ ReverseTransform
Transform from destination to source CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition: qgsrange.h:247
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
The QgsLabelingEngine class provides map labeling functionality.
virtual void run(QgsRenderContext &context)=0
Runs the labeling job.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
virtual bool isVisibleInZRange(const QgsDoubleRange &range) const
Returns true if the layer should be visible and rendered for the specified z range.
virtual void setLayerRenderingTimeHint(int time)
Sets approximate render time (in ms) for the layer to render.
Restore overridden layer style on destruction.
virtual bool isVisibleInTemporalRange(const QgsDateTimeRange &range) const
Returns true if the layer should be visible and rendered for the specified time range.
Base class for all map layer types.
Definition: qgsmaplayer.h:70
QString name
Definition: qgsmaplayer.h:73
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QgsMapLayerType type
Definition: qgsmaplayer.h:77
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:76
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
bool isValid
Definition: qgsmaplayer.h:78
double minimumScale() const
Returns the minimum map scale (i.e.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1265
double opacity
Definition: qgsmaplayer.h:79
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1258
double maximumScale() const
Returns the maximum map scale (i.e.
This class is responsible for keeping cache of rendered images resulting from a map rendering job.
bool updateParameters(const QgsRectangle &extent, const QgsMapToPixel &mtp)
Sets extent and scale parameters.
QList< QgsMapLayer * > dependentLayers(const QString &cacheKey) const
Returns a list of map layers on which an image in the cache depends.
bool hasCacheImage(const QString &cacheKey) const
Returns true if the cache contains an image with the specified cacheKey that has the same extent and ...
QImage cacheImage(const QString &cacheKey) const
Returns the cached image for the specified cacheKey.
bool hasAnyCacheImage(const QString &cacheKey, double minimumScaleThreshold=0, double maximumScaleThreshold=0) const
Returns true if the cache contains an image with the specified cacheKey with any cache's parameters (...
void setCacheImageWithParameters(const QString &cacheKey, const QImage &image, const QgsRectangle &extent, const QgsMapToPixel &mapToPixel, const QList< QgsMapLayer * > &dependentLayers=QList< QgsMapLayer * >())
Set the cached image for a particular cacheKey, using a specific extent and mapToPixel (which may dif...
void clearCacheImage(const QString &cacheKey)
Removes an image from the cache with matching cacheKey.
QImage transformedCacheImage(const QString &cacheKey, const QgsMapToPixel &mtp) const
Returns the cached image for the specified cacheKey transformed to the particular extent and scale.
Abstract base class for map rendering implementations.
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
static QImage layerImageToBeComposed(const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache)
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
QHash< QString, int > mLayerRenderingTimeHints
Approximate expected layer rendering time per layer, by layer ID.
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
static const QgsSettingsEntryBool settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
static const QString LABEL_PREVIEW_CACHE_ID
QgsMapRendererCache ID string for cached label image during preview compositions only.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
QgsMapRendererCache * mCache
void finished()
emitted when asynchronous rendering is finished (or canceled).
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
QgsMapSettings mSettings
QgsMapRendererJob(const QgsMapSettings &settings)
void start()
Start the rendering job and immediately return.
QList< QgsMapRendererJob::Error > Errors
static const QString LABEL_CACHE_ID
QgsMapRendererCache ID string for cached label image.
QHash< QgsWeakMapLayerPointer, int > mPerLayerRenderingTime
Render time (in ms) per layer, by layer ID.
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
void cleanupJobs(LayerRenderJobs &jobs)
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
QgsMapRendererQImageJob(const QgsMapSettings &settings)
The QgsMapSettings class contains configuration for rendering of the map.
QSize deviceOutputSize() const
Returns the device output size of the map render.
double scale() const
Returns the calculated map scale.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Enable anti-aliasing for map rendering.
QColor backgroundColor() const
Returns the background color of the map.
float devicePixelRatio() const
Returns the device pixel ratio.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
double extentBuffer() const
Returns the buffer in map units to use around the visible extent for rendering symbols whose correspo...
const QgsMapToPixel & mapToPixel() const
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
QList< QgsMapLayer * > layers() const
Returns the list of layers which will be rendered in the map.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
QMap< QString, QString > layerStyleOverrides() const
Returns the map of map layer style overrides (key: layer ID, value: style name) where a different sty...
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
A class to represent a 2D point.
Definition: qgspointxy.h:59
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
Definition: qgspointxy.cpp:51
Q_GADGET double x
Definition: qgspointxy.h:62
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Definition: qgsrectangle.h:559
Contains information about the context of a rendering operation.
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Type used to refer to a specific symbol layer in a symbol of a layer.
static QSet< const QgsSymbolLayer * > toSymbolLayerPointers(QgsFeatureRenderer *renderer, const QSet< QgsSymbolLayerId > &symbolLayerIds)
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature...
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
bool isTemporal() const
Returns true if the object's temporal range is enabled, and the object will be filtered when renderin...
Implementation of threaded rendering for vector layers.
QgsFeatureRenderer * featureRenderer()
Returns the feature renderer.
static QHash< QString, QHash< QString, QSet< QgsSymbolLayerId > > > labelMasks(const QgsVectorLayer *)
Returns masks defined in labeling options of a layer.
static QHash< QString, QSet< QgsSymbolLayerId > > symbolLayerMasks(const QgsVectorLayer *)
Returns all masks that may be defined on symbol layers for a given vector layer.
Represents a vector layer which manages a vector based data sets.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext) FINAL
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
@ PointCloudLayer
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38