QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 #include "qgsrendereditemresults.h"
47 
49 
50 const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
51 const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
52 
53 LayerRenderJob &LayerRenderJob::operator=( LayerRenderJob &&other )
54 {
55  mContext = std::move( other.mContext );
56 
57  img = other.img;
58  other.img = nullptr;
59 
60  renderer = other.renderer;
61  other.renderer = nullptr;
62 
63  imageInitialized = other.imageInitialized;
64  blendMode = other.blendMode;
65  opacity = other.opacity;
66  cached = other.cached;
67  layer = other.layer;
68  completed = other.completed;
69  renderingTime = other.renderingTime;
70  estimatedRenderingTime = other.estimatedRenderingTime ;
71  errors = other.errors;
72  layerId = other.layerId;
73 
74  maskImage = other.maskImage;
75  other.maskImage = nullptr;
76 
77  firstPassJob = other.firstPassJob;
78  other.firstPassJob = nullptr;
79 
80  maskJobs = other.maskJobs;
81 
82  return *this;
83 }
84 
85 LayerRenderJob::LayerRenderJob( LayerRenderJob &&other )
86  : imageInitialized( other.imageInitialized )
87  , blendMode( other.blendMode )
88  , opacity( other.opacity )
89  , cached( other.cached )
90  , layer( other.layer )
91  , completed( other.completed )
92  , renderingTime( other.renderingTime )
93  , estimatedRenderingTime( other.estimatedRenderingTime )
94  , errors( other.errors )
95  , layerId( other.layerId )
96  , maskJobs( other.maskJobs )
97 {
98  mContext = std::move( other.mContext );
99 
100  img = other.img;
101  other.img = nullptr;
102 
103  renderer = other.renderer;
104  other.renderer = nullptr;
105 
106  maskImage = other.maskImage;
107  other.maskImage = nullptr;
108 
109  firstPassJob = other.firstPassJob;
110  other.firstPassJob = nullptr;
111 }
112 
113 bool LayerRenderJob::imageCanBeComposed() const
114 {
115  if ( imageInitialized )
116  {
117  if ( renderer )
118  {
119  return renderer->isReadyToCompose();
120  }
121  else
122  {
123  return true;
124  }
125  }
126  else
127  {
128  return false;
129  }
130 }
131 
133  : mSettings( settings )
134  , mRenderedItemResults( std::make_unique< QgsRenderedItemResults >( settings.extent() ) )
135 {}
136 
138 
140 {
141  if ( mSettings.hasValidSettings() )
142  startPrivate();
143  else
144  {
145  mErrors.append( QgsMapRendererJob::Error( QString(), tr( "Invalid map settings" ) ) );
146  emit finished();
147  }
148 }
149 
151 {
153 }
154 
156 {
157  return mRenderedItemResults.release();
158 }
159 
161  : QgsMapRendererJob( settings )
162 {
163 }
164 
165 
167 {
168  return mErrors;
169 }
170 
172 {
173  mCache = cache;
174 }
175 
176 QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
177 {
178  QHash<QgsMapLayer *, int> result;
179  for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
180  {
181  if ( auto &&lKey = it.key() )
182  result.insert( lKey, it.value() );
183  }
184  return result;
185 }
186 
187 void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
188 {
189  mLayerRenderingTimeHints = hints;
190 }
191 
193 {
194  return mSettings;
195 }
196 
198 {
199  bool canCache = mCache;
200 
201  // calculate which layers will be labeled
202  QSet< QgsMapLayer * > labeledLayers;
203  const QList<QgsMapLayer *> layers = mSettings.layers();
204  for ( QgsMapLayer *ml : layers )
205  {
207  labeledLayers << ml;
208 
209  switch ( ml->type() )
210  {
212  {
213  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
214  if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
215  {
216  canCache = false;
217  }
218  break;
219  }
220 
222  {
223  // TODO -- add detection of advanced labeling effects for vector tile layers
224  break;
225  }
226 
232  break;
233  }
234 
235  if ( !canCache )
236  break;
237 
238  }
239 
241  {
242  // we may need to clear label cache and re-register labeled features - check for that here
243 
244  // can we reuse the cached label solution?
245  bool canUseCache = canCache && qgis::listToSet( mCache->dependentLayers( LABEL_CACHE_ID ) ) == labeledLayers;
246  if ( !canUseCache )
247  {
248  // no - participating layers have changed
250  }
251  }
252  return canCache;
253 }
254 
255 
256 bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
257 {
258  bool res = true;
259  // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
260  // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
261  QgsCoordinateTransform approxTransform = ct;
262  approxTransform.setBallparkTransformsAreAppropriate( true );
263 
264  try
265  {
266 #ifdef QGISDEBUG
267  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
268 #endif
269  // Split the extent into two if the source CRS is
270  // geographic and the extent crosses the split in
271  // geographic coordinates (usually +/- 180 degrees,
272  // and is assumed to be so here), and draw each
273  // extent separately.
274  static const double SPLIT_COORD = 180.0;
275 
276  if ( ml->crs().isGeographic() )
277  {
278  if ( ml->type() == QgsMapLayerType::VectorLayer && !approxTransform.destinationCrs().isGeographic() )
279  {
280  // if we transform from a projected coordinate system check
281  // check if transforming back roughly returns the input
282  // extend - otherwise render the world.
283  QgsRectangle extent1 = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
284  QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, Qgis::TransformDirection::Forward );
285 
286  QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
287  .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
288  .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
289  .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
290  .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
291  , 3 );
292 
293  // can differ by a maximum of up to 20% of height/width
294  if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
295  && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
296  && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
297  && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
298  )
299  {
300  extent = extent1;
301  }
302  else
303  {
304  extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
305  res = false;
306  }
307  }
308  else
309  {
310  // Note: ll = lower left point
311  QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
312  Qgis::TransformDirection::Reverse );
313 
314  // and ur = upper right point
315  QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
316  Qgis::TransformDirection::Reverse );
317 
318  QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
319 
320  extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
321 
322  QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
323 
324  if ( ll.x() > ur.x() )
325  {
326  // the coordinates projected in reverse order than what one would expect.
327  // we are probably looking at an area that includes longitude of 180 degrees.
328  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
329  // so let's use (-180,180). This hopefully does not add too much overhead. It is
330  // more straightforward than rendering with two separate extents and more consistent
331  // for rendering, labeling and caching as everything is rendered just in one go
332  extent.setXMinimum( -SPLIT_COORD );
333  extent.setXMaximum( SPLIT_COORD );
334  res = false;
335  }
336  }
337 
338  // TODO: the above rule still does not help if using a projection that covers the whole
339  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
340  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
341  // but in fact the extent should cover the whole world.
342  }
343  else // can't cross 180
344  {
345  if ( approxTransform.destinationCrs().isGeographic() &&
346  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
347  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
348  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
349  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
350  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
351  // but this seems like a safer choice.
352  {
353  extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
354  res = false;
355  }
356  else
357  extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
358  }
359  }
360  catch ( QgsCsException & )
361  {
362  QgsDebugMsg( QStringLiteral( "Transform error caught" ) );
363  extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
364  r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
365  res = false;
366  }
367 
368  return res;
369 }
370 
371 QImage *QgsMapRendererJob::allocateImage( QString layerId )
372 {
373  QImage *image = new QImage( mSettings.deviceOutputSize(),
375  image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
376  image->setDotsPerMeterX( 1000 * mSettings.outputDpi() / 25.4 );
377  image->setDotsPerMeterY( 1000 * mSettings.outputDpi() / 25.4 );
378  if ( image->isNull() )
379  {
380  mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
381  delete image;
382  return nullptr;
383  }
384  return image;
385 }
386 
387 QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image )
388 {
389  QPainter *painter = nullptr;
390  image = allocateImage( layerId );
391  if ( image )
392  {
393  painter = new QPainter( image );
394  painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing ) );
395 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
396  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
397 #endif
398  }
399  return painter;
400 }
401 
402 std::vector<LayerRenderJob> QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
403 {
404  std::vector< LayerRenderJob > layerJobs;
405 
406  // render all layers in the stack, starting at the base
407  QListIterator<QgsMapLayer *> li( mSettings.layers() );
408  li.toBack();
409 
410  if ( mCache )
411  {
413  Q_UNUSED( cacheValid )
414  QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
415  }
416 
417  bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
418 
419  while ( li.hasPrevious() )
420  {
421  QgsMapLayer *ml = li.previous();
422 
423  QgsDebugMsgLevel( QStringLiteral( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
424  .arg( ml->name() )
425  .arg( ml->minimumScale() )
426  .arg( ml->maximumScale() )
427  .arg( ml->hasScaleBasedVisibility() )
428  .arg( ml->blendMode() )
429  .arg( ml->isValid() )
430  , 3 );
431 
432  if ( !ml->isValid() )
433  {
434  QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
435  continue;
436  }
437 
438  if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
439  {
440  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
441  continue;
442  }
443 
445  {
446  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
447  continue;
448  }
449 
451  {
452  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
453  continue;
454  }
455 
457  r1.grow( mSettings.extentBuffer() );
459 
460  ct = mSettings.layerTransform( ml );
461  bool haveExtentInLayerCrs = true;
462  if ( ct.isValid() )
463  {
464  haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
465  }
466  QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
467  if ( !r1.isFinite() || !r2.isFinite() )
468  {
469  mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
470  continue;
471  }
472 
473  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
474 
475  // Force render of layers that are being edited
476  // or if there's a labeling engine that needs the layer to register features
477  if ( mCache )
478  {
479  const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
480  if ( ( vl && vl->isEditable() ) || requiresLabeling )
481  {
482  mCache->clearCacheImage( ml->id() );
483  }
484  }
485 
486  layerJobs.emplace_back( LayerRenderJob() );
487  LayerRenderJob &job = layerJobs.back();
488  job.layer = ml;
489  job.layerId = ml->id();
490  job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
491 
492  job.setContext( std::make_unique< QgsRenderContext >( QgsRenderContext::fromMapSettings( mSettings ) ) );
493  job.context()->expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
494  job.context()->setPainter( painter );
495  job.context()->setLabelingEngine( labelingEngine2 );
496  job.context()->setCoordinateTransform( ct );
497  job.context()->setExtent( r1 );
498  if ( !haveExtentInLayerCrs )
499  job.context()->setFlag( Qgis::RenderContextFlag::ApplyClipAfterReprojection, true );
500 
501  if ( mFeatureFilterProvider )
502  job.context()->setFeatureFilterProvider( mFeatureFilterProvider );
503 
504  QgsMapLayerStyleOverride styleOverride( ml );
505  if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
506  styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
507 
508  job.blendMode = ml->blendMode();
509 
510  // raster layer opacity is handled directly within the raster layer renderer, so don't
511  // apply default opacity handling here!
512  job.opacity = ml->type() != QgsMapLayerType::RasterLayer ? ml->opacity() : 1.0;
513 
514  // if we can use the cache, let's do it and avoid rendering!
515  if ( mCache && mCache->hasCacheImage( ml->id() ) )
516  {
517  job.cached = true;
518  job.imageInitialized = true;
519  job.img = new QImage( mCache->cacheImage( ml->id() ) );
520  job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
521  job.renderer = nullptr;
522  job.context()->setPainter( nullptr );
523  mLayersRedrawnFromCache.append( ml->id() );
524  continue;
525  }
526 
527  QElapsedTimer layerTime;
528  layerTime.start();
529  job.renderer = ml->createMapRenderer( *( job.context() ) );
530  if ( job.renderer )
531  {
532  job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
533  job.context()->setFeedback( job.renderer->feedback() );
534  }
535 
536  // If we are drawing with an alternative blending mode then we need to render to a separate image
537  // before compositing this on the map. This effectively flattens the layer and prevents
538  // blending occurring between objects on the layer
539  if ( mCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
540  {
541  // Flattened image for drawing when a blending mode is set
542  job.context()->setPainter( allocateImageAndPainter( ml->id(), job.img ) );
543  if ( ! job.img )
544  {
545  delete job.renderer;
546  job.renderer = nullptr;
547  layerJobs.pop_back();
548  continue;
549  }
550  }
551 
552  job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
553  }
554 
555  return layerJobs;
556 }
557 
558 std::vector< LayerRenderJob > QgsMapRendererJob::prepareSecondPassJobs( std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob )
559 {
560  std::vector< LayerRenderJob > secondPassJobs;
561 
562  // We will need to quickly access the associated rendering job of a layer
563  QHash<QString, LayerRenderJob *> layerJobMapping;
564 
565  // ... and whether a layer has a mask defined
566  QSet<QString> layerHasMask;
567 
568  struct MaskSource
569  {
570  QString layerId;
571  QString labelRuleId;
572  int labelMaskId;
573  MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_ ):
574  layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ) {}
575  };
576 
577  // We collect for each layer, the set of symbol layers that will be "masked"
578  // and the list of source layers that have a mask
579  QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
580 
581  // First up, create a mapping of layer id to jobs. We need this to filter out any masking
582  // which refers to layers which we aren't rendering as part of this map render
583  for ( LayerRenderJob &job : firstPassJobs )
584  {
585  layerJobMapping[job.layerId] = &job;
586  }
587 
588  // next, collate a master list of masked layers, skipping over any which refer to layers
589  // which don't have a corresponding render job
590  for ( LayerRenderJob &job : firstPassJobs )
591  {
592  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
593  if ( ! vl )
594  continue;
595 
596  // lambda function to factor code for both label masks and symbol layer masks
597  auto collectMasks = [&]( QHash<QString, QSet<QgsSymbolLayerId>> *masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
598  {
599  for ( auto it = masks->begin(); it != masks->end(); ++it )
600  {
601  auto lit = maskedSymbolLayers.find( it.key() );
602  if ( lit == maskedSymbolLayers.end() )
603  {
604  maskedSymbolLayers[it.key()] = qMakePair( it.value(), QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId ) );
605  }
606  else
607  {
608  if ( lit->first != it.value() )
609  {
610  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() ) );
611  continue;
612  }
613  lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId ) );
614  }
615  }
616  if ( ! masks->isEmpty() )
617  layerHasMask.insert( sourceLayerId );
618  };
619 
620  // collect label masks
621  QHash<QString, QHash<QString, QSet<QgsSymbolLayerId>>> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
622  for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
623  {
624  QString labelRule = it.key();
625  // this is a hash of layer id to masks
626  QHash<QString, QSet<QgsSymbolLayerId>> masks = it.value();
627 
628  // filter out masks to those which we are actually rendering
629  QHash<QString, QSet<QgsSymbolLayerId>> usableMasks;
630  for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
631  {
632  const QString sourceLayerId = mit.key();
633  // if we aren't rendering the source layer as part of this render, we can't process this mask
634  if ( !layerJobMapping.contains( sourceLayerId ) )
635  continue;
636  else
637  usableMasks.insert( sourceLayerId, mit.value() );
638  }
639 
640  if ( usableMasks.empty() )
641  continue;
642 
643  // group layers by QSet<QgsSymbolLayerReference>
644  QSet<QgsSymbolLayerReference> slRefs;
645  for ( auto mit = usableMasks.begin(); mit != usableMasks.end(); mit++ )
646  {
647  const QString sourceLayerId = mit.key();
648  // if we aren't rendering the source layer as part of this render, we can't process this mask
649  if ( !layerJobMapping.contains( sourceLayerId ) )
650  continue;
651 
652  for ( auto slIt = mit.value().begin(); slIt != mit.value().end(); slIt++ )
653  {
654  slRefs.insert( QgsSymbolLayerReference( mit.key(), *slIt ) );
655  }
656  }
657  // generate a new mask id for this set
658  int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
659 
660  // now collect masks
661  collectMasks( &usableMasks, vl->id(), labelRule, labelMaskId );
662  }
663 
664  // collect symbol layer masks
665  QHash<QString, QSet<QgsSymbolLayerId>> symbolLayerMasks = QgsVectorLayerUtils::symbolLayerMasks( vl );
666  collectMasks( &symbolLayerMasks, vl->id() );
667  }
668 
669  if ( maskedSymbolLayers.isEmpty() )
670  return secondPassJobs;
671 
672  // Now that we know some layers have a mask, we have to allocate a mask image and painter
673  // for them in the first pass job
674  for ( LayerRenderJob &job : firstPassJobs )
675  {
676  if ( job.img == nullptr )
677  {
678  job.context()->setPainter( allocateImageAndPainter( job.layerId, job.img ) );
679  }
680  if ( layerHasMask.contains( job.layerId ) )
681  {
682  // Note: we only need an alpha channel here, rather than a full RGBA image
683  job.context()->setMaskPainter( allocateImageAndPainter( job.layerId, job.maskImage ) );
684  job.maskImage->fill( 0 );
685  }
686  }
687 
688  // Allocate an image for labels
689  if ( labelJob.img == nullptr )
690  {
691  labelJob.img = allocateImage( QStringLiteral( "labels" ) );
692  }
693 
694  // Prepare label mask images
695  for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
696  {
697  QImage *maskImage;
698  labelJob.context.setMaskPainter( allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage ), maskId );
699  maskImage->fill( 0 );
700  labelJob.maskImages.push_back( maskImage );
701  }
702  labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
703 
704  // Prepare second pass jobs
705  for ( LayerRenderJob &job : firstPassJobs )
706  {
707  QgsMapLayer *ml = job.layer;
708 
709  auto it = maskedSymbolLayers.find( ml->id() );
710  if ( it == maskedSymbolLayers.end() )
711  continue;
712 
713  QList<MaskSource> &sourceList = it->second;
714  const QSet<QgsSymbolLayerId> &symbolList = it->first;
715 
716  secondPassJobs.emplace_back( LayerRenderJob() );
717  LayerRenderJob &job2 = secondPassJobs.back();
718 
719  // copy the context from the initial job
720  job2.setContext( std::make_unique< QgsRenderContext >( *job.context() ) );
721  // also assign layer to match initial job
722  job2.layer = job.layer;
723  job2.layerId = job.layerId;
724  // associate first pass job with second pass job
725  job2.firstPassJob = &job;
726 
727  QgsVectorLayer *vl1 = qobject_cast<QgsVectorLayer *>( job.layer );
728 
729  // create a new destination image for the second pass job, and update
730  // second pass job context accordingly
731  job2.context()->setMaskPainter( nullptr );
732  job2.context()->setPainter( allocateImageAndPainter( job.layerId, job2.img ) );
733  if ( ! job2.img )
734  {
735  secondPassJobs.pop_back();
736  continue;
737  }
738 
739  // Points to the first pass job. This will be needed during the second pass composition.
740  for ( MaskSource &source : sourceList )
741  {
742  if ( source.labelMaskId != -1 )
743  job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
744  else
745  job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
746  }
747 
748  // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
749  // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
750  QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( vl1->createMapRenderer( *job2.context() ) );
751  job2.renderer = mapRenderer;
752  if ( job2.renderer )
753  {
754  job2.context()->setFeedback( job2.renderer->feedback() );
755  }
756 
757  // Modify the render context so that symbol layers get disabled as needed.
758  // The map renderer stores a reference to the context, so we can modify it even after the map renderer creation (what we need here)
759  job2.context()->setDisabledSymbolLayers( QgsSymbolLayerUtils::toSymbolLayerPointers( mapRenderer->featureRenderer(), symbolList ) );
760  }
761 
762  return secondPassJobs;
763 }
764 
765 LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
766 {
767  LabelRenderJob job;
769  job.context.setPainter( painter );
770  job.context.setLabelingEngine( labelingEngine2 );
771 
773  r1.grow( mSettings.extentBuffer() );
774  job.context.setExtent( r1 );
775 
776  job.context.setFeatureFilterProvider( mFeatureFilterProvider );
779  job.context.setCoordinateTransform( ct );
780 
781  // if we can use the cache, let's do it and avoid rendering!
782  bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
783  if ( hasCache )
784  {
785  job.cached = true;
786  job.complete = true;
787  job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
788  Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
789  job.context.setPainter( nullptr );
790  }
791  else
792  {
793  if ( canUseLabelCache && ( mCache || !painter ) )
794  {
795  job.img = allocateImage( QStringLiteral( "labels" ) );
796  }
797  }
798 
799  return job;
800 }
801 
802 
803 void QgsMapRendererJob::cleanupJobs( std::vector<LayerRenderJob> &jobs )
804 {
805  for ( LayerRenderJob &job : jobs )
806  {
807  if ( job.img )
808  {
809  delete job.context()->painter();
810  job.context()->setPainter( nullptr );
811 
812  if ( mCache && !job.cached && job.completed && job.layer )
813  {
814  QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
815  mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
816  mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
817  }
818 
819  delete job.img;
820  job.img = nullptr;
821  }
822 
823  // delete the mask image and painter
824  if ( job.maskImage )
825  {
826  delete job.context()->maskPainter();
827  job.context()->setMaskPainter( nullptr );
828  delete job.maskImage;
829  }
830 
831  if ( job.renderer )
832  {
833  const QStringList errors = job.renderer->errors();
834  for ( const QString &message : errors )
835  mErrors.append( Error( job.renderer->layerId(), message ) );
836 
837  mRenderedItemResults->appendResults( job.renderer->takeRenderedItemDetails(), *job.context() );
838 
839  delete job.renderer;
840  job.renderer = nullptr;
841  }
842 
843  if ( job.layer )
844  mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
845  }
846 
847  jobs.clear();
848 }
849 
850 void QgsMapRendererJob::cleanupSecondPassJobs( std::vector< LayerRenderJob > &jobs )
851 {
852  for ( LayerRenderJob &job : jobs )
853  {
854  if ( job.img )
855  {
856  delete job.context()->painter();
857  job.context()->setPainter( nullptr );
858 
859  delete job.img;
860  job.img = nullptr;
861  }
862 
863  if ( job.renderer )
864  {
865  delete job.renderer;
866  job.renderer = nullptr;
867  }
868 
869  if ( job.layer )
870  mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
871  }
872 
873  jobs.clear();
874 }
875 
876 void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
877 {
878  if ( job.img )
879  {
880  if ( mCache && !job.cached && !job.context.renderingStopped() )
881  {
882  QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
883  mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
884  mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
885  }
886 
887  delete job.img;
888  job.img = nullptr;
889  }
890 
891  for ( int maskId = 0; maskId < job.maskImages.size(); maskId++ )
892  {
893  delete job.context.maskPainter( maskId );
894  job.context.setMaskPainter( nullptr, maskId );
895  delete job.maskImages[maskId];
896  }
897 }
898 
899 
900 #define DEBUG_RENDERING 0
901 
902 QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings,
903  const std::vector<LayerRenderJob> &jobs,
904  const LabelRenderJob &labelJob,
905  const QgsMapRendererCache *cache
906  )
907 {
908  QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
909  image.setDevicePixelRatio( settings.devicePixelRatio() );
910  image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
911  image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
912  image.fill( settings.backgroundColor().rgba() );
913 
914  QPainter painter( &image );
915 
916 #if DEBUG_RENDERING
917  int i = 0;
918 #endif
919  for ( const LayerRenderJob &job : jobs )
920  {
921  if ( job.layer && job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
922  continue; // skip layer for now, it will be rendered after labels
923 
924  QImage img = layerImageToBeComposed( settings, job, cache );
925  if ( img.isNull() )
926  continue; // image is not prepared and not even in cache
927 
928  painter.setCompositionMode( job.blendMode );
929  painter.setOpacity( job.opacity );
930 
931 #if DEBUG_RENDERING
932  img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
933  i++;
934 #endif
935 
936  painter.drawImage( 0, 0, img );
937  }
938 
939  // IMPORTANT - don't draw labelJob img before the label job is complete,
940  // as the image is uninitialized and full of garbage before the label job
941  // commences
942  if ( labelJob.img && labelJob.complete )
943  {
944  painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
945  painter.setOpacity( 1.0 );
946  painter.drawImage( 0, 0, *labelJob.img );
947  }
948  // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
949  // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
950  // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
951  else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
952  {
953  const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
954  painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
955  painter.setOpacity( 1.0 );
956  painter.drawImage( 0, 0, labelCacheImage );
957  }
958 
959  // render any layers with the renderAboveLabels flag now
960  for ( const LayerRenderJob &job : jobs )
961  {
962  if ( !job.layer || !job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
963  continue;
964 
965  QImage img = layerImageToBeComposed( settings, job, cache );
966  if ( img.isNull() )
967  continue; // image is not prepared and not even in cache
968 
969  painter.setCompositionMode( job.blendMode );
970  painter.setOpacity( job.opacity );
971 
972  painter.drawImage( 0, 0, img );
973  }
974 
975  painter.end();
976 #if DEBUG_RENDERING
977  image.save( "/tmp/final.png" );
978 #endif
979  return image;
980 }
981 
983  const QgsMapSettings &settings,
984  const LayerRenderJob &job,
985  const QgsMapRendererCache *cache
986 )
987 {
988  if ( job.imageCanBeComposed() )
989  {
990  Q_ASSERT( job.img );
991  return *job.img;
992  }
993  else
994  {
995  if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
996  {
997  return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
998  }
999  else
1000  return QImage();
1001  }
1002 }
1003 
1004 void QgsMapRendererJob::composeSecondPass( std::vector<LayerRenderJob> &secondPassJobs, LabelRenderJob &labelJob )
1005 {
1006 #if DEBUG_RENDERING
1007  int i = 0;
1008 #endif
1009  // compose the second pass with the mask
1010  for ( LayerRenderJob &job : secondPassJobs )
1011  {
1012 #if DEBUG_RENDERING
1013  i++;
1014  job.img->save( QString( "/tmp/second_%1.png" ).arg( i ) );
1015  int mask = 0;
1016 #endif
1017 
1018  // Merge all mask images into the first one if we have more than one mask image
1019  if ( job.maskJobs.size() > 1 )
1020  {
1021  QPainter *maskPainter = nullptr;
1022  for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
1023  {
1024  QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
1025 #if DEBUG_RENDERING
1026  maskImage->save( QString( "/tmp/mask_%1_%2.png" ).arg( i ).arg( mask++ ) );
1027 #endif
1028  if ( ! maskPainter )
1029  {
1030  maskPainter = p.first ? p.first->context()->maskPainter() : labelJob.context.maskPainter( p.second );
1031  }
1032  else
1033  {
1034  maskPainter->drawImage( 0, 0, *maskImage );
1035  }
1036  }
1037  }
1038 
1039  if ( ! job.maskJobs.isEmpty() )
1040  {
1041  // All have been merged into the first
1042  QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
1043  QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
1044 #if DEBUG_RENDERING
1045  maskImage->save( QString( "/tmp/mask_%1.png" ).arg( i ) );
1046 #endif
1047 
1048  // Only retain parts of the second rendering that are "inside" the mask image
1049  QPainter *painter = job.context()->painter();
1050  painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1051 
1052  //Create an "alpha binarized" image of the maskImage to :
1053  //* Eliminate antialiasing artifact
1054  //* Avoid applying mask opacity to elements under the mask but not masked
1055  QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
1056  QVector<QRgb> mswTable;
1057  mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
1058  mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
1059  maskBinAlpha.setColorTable( mswTable );
1060  painter->drawImage( 0, 0, maskBinAlpha );
1061 #if DEBUG_RENDERING
1062  job.img->save( QString( "/tmp/second_%1_a.png" ).arg( i ) );
1063 #endif
1064 
1065  // Modify the first pass' image ...
1066  {
1067  QPainter tempPainter;
1068 
1069  // reuse the first pass painter, if available
1070  QPainter *painter1 = job.firstPassJob->context()->painter();
1071  if ( ! painter1 )
1072  {
1073  tempPainter.begin( job.firstPassJob->img );
1074  painter1 = &tempPainter;
1075  }
1076 #if DEBUG_RENDERING
1077  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_1.png" ).arg( i ) );
1078 #endif
1079  // ... first retain parts that are "outside" the mask image
1080  painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
1081  painter1->drawImage( 0, 0, *maskImage );
1082 
1083 #if DEBUG_RENDERING
1084  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_2.png" ).arg( i ) );
1085 #endif
1086  // ... and overpaint the second pass' image on it
1087  painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
1088  painter1->drawImage( 0, 0, *job.img );
1089 #if DEBUG_RENDERING
1090  job.img->save( QString( "/tmp/second_%1_b.png" ).arg( i ) );
1091  if ( job.firstPassJob )
1092  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_3.png" ).arg( i ) );
1093 #endif
1094  }
1095  }
1096  }
1097 }
1098 
1099 void QgsMapRendererJob::logRenderingTime( const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob )
1100 {
1102  return;
1103 
1104  QMultiMap<int, QString> elapsed;
1105  for ( const LayerRenderJob &job : jobs )
1106  elapsed.insert( job.renderingTime, job.layerId );
1107  for ( const LayerRenderJob &job : secondPassJobs )
1108  elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
1109 
1110  elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
1111 
1112  QList<int> tt( elapsed.uniqueKeys() );
1113  std::sort( tt.begin(), tt.end(), std::greater<int>() );
1114  for ( int t : std::as_const( tt ) )
1115  {
1116  QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
1117  }
1118  QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
1119 }
1120 
1121 void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1122 {
1123  QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1124 
1125  QElapsedTimer t;
1126  t.start();
1127 
1128  // Reset the composition mode before rendering the labels
1129  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1130 
1131  renderContext.setPainter( painter );
1132 
1133  if ( labelingEngine2 )
1134  {
1135  labelingEngine2->run( renderContext );
1136  }
1137 
1138  QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1139 }
1140 
1141 void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1142 {
1143  Q_UNUSED( settings )
1144 
1145  drawLabeling( renderContext, labelingEngine2, painter );
1146 }
1147 
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Enable anti-aliasing for map rendering.
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.
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.
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...
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
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:73
QString name
Definition: qgsmaplayer.h:76
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
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:81
double minimumScale() const
Returns the minimum map scale (i.e.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1493
double opacity
Definition: qgsmaplayer.h:82
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1486
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 logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
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 setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
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)
QHash< QString, int > mLayerRenderingTimeHints
Approximate expected layer rendering time per layer, by layer ID.
std::unique_ptr< QgsRenderedItemResults > mRenderedItemResults
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)
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.
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
QgsMapRendererCache * mCache
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapSettings mSettings
QgsMapRendererJob(const QgsMapSettings &settings)
~QgsMapRendererJob() override
void start()
Start the rendering job and immediately return.
QStringList mLayersRedrawnFromCache
QStringList layersRedrawnFromCache() const
Returns a list of the layer IDs for all layers which were redrawn from cached images.
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.
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 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...
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
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.
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 output image size into account.
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.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
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.
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
Stores collated details of rendered items during a map rendering operation.
bool value(const QString &dynamicKeyPart=QString(), bool useDefaultValueOverride=false, bool defaultValueOverride=false) const
Returns settings value.
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:1246
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38