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