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