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