QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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 <memory>
19
20#include "qgselevationmap.h"
21#include "qgsexception.h"
24#include "qgsgeos.h"
25#include "qgslabelingengine.h"
26#include "qgslogger.h"
27#include "qgsmaplayer.h"
30#include "qgsmaplayerrenderer.h"
31#include "qgsmaplayerstyle.h"
33#include "qgsmaprenderercache.h"
34#include "qgsmeshlayer.h"
36#include "qgsmessagelog.h"
37#include "qgspainting.h"
38#include "qgspallabeling.h"
39#include "qgsrasterlabeling.h"
40#include "qgsrasterlayer.h"
41#include "qgsrasterrenderer.h"
42#include "qgsrendercontext.h"
44#include "qgsruntimeprofiler.h"
46#include "qgssettingstree.h"
49#include "qgsvectorlayerutils.h"
50
51#include <QElapsedTimer>
52#include <QPainter>
53#include <QPicture>
54#include <QTimer>
55#include <QtConcurrentMap>
56
57#include "moc_qgsmaprendererjob.cpp"
58
60const QgsSettingsEntryString *QgsMapRendererJob::settingsMaskBackend = new QgsSettingsEntryString( QStringLiteral( "mask-backend" ), QgsSettingsTree::sTreeMap, QString(), QStringLiteral( "Backend engine to use for selective masking" ) );
61
63
64const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
65const QString QgsMapRendererJob::ELEVATION_MAP_CACHE_PREFIX = QStringLiteral( "_elevation_map_" );
66const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
67
68LayerRenderJob &LayerRenderJob::operator=( LayerRenderJob &&other )
69{
70 if ( &other == this )
71 return *this;
72
73 mContext = std::move( other.mContext );
74
75 img = other.img;
76 other.img = nullptr;
77
78 renderer = other.renderer;
79 other.renderer = nullptr;
80
81 previewRenderImage = other.previewRenderImage;
82 other.previewRenderImage = nullptr;
83
84 imageInitialized = other.imageInitialized;
85 previewRenderImageInitialized = other.previewRenderImageInitialized;
86
87 blendMode = other.blendMode;
88 opacity = other.opacity;
89 cached = other.cached;
90 layer = other.layer;
91 renderAboveLabels = other.renderAboveLabels;
92 completed = other.completed;
93 renderingTime = other.renderingTime;
94 estimatedRenderingTime = other.estimatedRenderingTime ;
95 errors = other.errors;
96 layerId = other.layerId;
97
98 maskPaintDevice = std::move( other.maskPaintDevice );
99
100 firstPassJob = other.firstPassJob;
101 other.firstPassJob = nullptr;
102
103 picture = std::move( other.picture );
104
105 maskJobs = other.maskJobs;
106
107 maskRequiresLayerRasterization = other.maskRequiresLayerRasterization;
108
109 elevationMap = other.elevationMap;
110 maskPainter = std::move( other.maskPainter );
111
112 return *this;
113}
114
115LayerRenderJob::LayerRenderJob( LayerRenderJob &&other )
116 : imageInitialized( other.imageInitialized )
117 , previewRenderImageInitialized( other.previewRenderImageInitialized )
118 , blendMode( other.blendMode )
119 , opacity( other.opacity )
120 , cached( other.cached )
121 , renderAboveLabels( other.renderAboveLabels )
122 , layer( other.layer )
123 , completed( other.completed )
124 , renderingTime( other.renderingTime )
125 , estimatedRenderingTime( other.estimatedRenderingTime )
126 , errors( other.errors )
127 , layerId( other.layerId )
128 , maskPainter( nullptr ) // should this be other.maskPainter??
129 , maskRequiresLayerRasterization( other.maskRequiresLayerRasterization )
130 , maskJobs( other.maskJobs )
131{
132 mContext = std::move( other.mContext );
133
134 img = other.img;
135 other.img = nullptr;
136
137 previewRenderImage = other.previewRenderImage;
138 other.previewRenderImage = nullptr;
139
140 renderer = other.renderer;
141 other.renderer = nullptr;
142
143 elevationMap = other.elevationMap;
144 other.elevationMap = nullptr;
145
146 maskPaintDevice = std::move( other.maskPaintDevice );
147
148 firstPassJob = other.firstPassJob;
149 other.firstPassJob = nullptr;
150
151 picture = std::move( other.picture );
152}
153
154bool LayerRenderJob::imageCanBeComposed() const
155{
156 if ( imageInitialized )
157 {
158 if ( renderer )
159 {
160 return renderer->isReadyToCompose();
161 }
162 else
163 {
164 return true;
165 }
166 }
167 else
168 {
169 return false;
170 }
171}
172
174 : mSettings( settings )
175 , mRenderedItemResults( std::make_unique< QgsRenderedItemResults >( settings.extent() ) )
176 , mLabelingEngineFeedback( new QgsLabelingEngineFeedback( this ) )
177{}
178
180
182{
183 if ( mSettings.hasValidSettings() )
184 startPrivate();
185 else
186 {
187 mErrors.append( QgsMapRendererJob::Error( QString(), tr( "Invalid map settings" ) ) );
188 emit finished();
189 }
190}
191
193{
195}
196
198{
199 return mRenderedItemResults.release();
200}
201
203 : QgsMapRendererJob( settings )
204{
205}
206
207
209{
210 return mErrors;
211}
212
214{
215 mCache = cache;
216}
217
219{
220 return mLabelingEngineFeedback;
221}
222
223QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
224{
225 QHash<QgsMapLayer *, int> result;
226 for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
227 {
228 if ( auto &&lKey = it.key() )
229 result.insert( lKey, it.value() );
230 }
231 return result;
232}
233
234void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
235{
237}
238
240{
241 return mSettings;
242}
243
245{
246 bool canCache = mCache;
247
248 // calculate which layers will be labeled
249 QSet< QgsMapLayer * > labeledLayers;
250 const QList<QgsMapLayer *> layers = mSettings.layers();
251 for ( QgsMapLayer *ml : layers )
252 {
254 labeledLayers << ml;
255
256 switch ( ml->type() )
257 {
259 {
260 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
261 if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
262 {
263 canCache = false;
264 }
265 break;
266 }
267
269 {
270 QgsMeshLayer *l = qobject_cast< QgsMeshLayer *>( ml );
271 if ( l->labelsEnabled() && l->labeling()->requiresAdvancedEffects() )
272 {
273 canCache = false;
274 }
275 break;
276 }
277
279 {
280 QgsRasterLayer *l = qobject_cast< QgsRasterLayer *>( ml );
281 if ( l->labelsEnabled() && l->labeling()->requiresAdvancedEffects() )
282 {
283 canCache = false;
284 }
285 break;
286 }
287
289 {
290 // TODO -- add detection of advanced labeling effects for vector tile layers
291 break;
292 }
293
299 break;
300 }
301
302 if ( !canCache )
303 break;
304
305 }
306
307 if ( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) )
308 {
309 // we may need to clear label cache and re-register labeled features - check for that here
310
311 // can we reuse the cached label solution?
312 const QList< QgsMapLayer * > labelDependentLayers = mCache->dependentLayers( LABEL_CACHE_ID );
313 bool canUseCache = canCache && QSet< QgsMapLayer * >( labelDependentLayers.begin(), labelDependentLayers.end() ) == labeledLayers;
314 if ( !canUseCache )
315 {
316 // no - participating layers have changed
317 mCache->clearCacheImage( LABEL_CACHE_ID );
318 }
319 }
320 return canCache;
321}
322
324{
325 const QList<QgsMapLayer *> layers = mSettings.layers();
326 for ( QgsMapLayer *ml : layers )
327 {
329 continue;
330
331 switch ( ml->type() )
332 {
334 {
335 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
337 {
338 return true;
339 }
340 break;
341 }
342
344 {
345 QgsMeshLayer *l = qobject_cast< QgsMeshLayer *>( ml );
347 {
348 return true;
349 }
350 break;
351 }
352
354 {
355 QgsRasterLayer *l = qobject_cast< QgsRasterLayer *>( ml );
357 {
358 return true;
359 }
360 break;
361 }
362
364 {
365 // TODO -- add detection of advanced labeling effects for vector tile layers
366 break;
367 }
368
374 break;
375 }
376 }
377
378 return false;
379}
380
381bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
382{
383 bool res = true;
384 // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
385 // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
386 QgsCoordinateTransform approxTransform = ct;
387 approxTransform.setBallparkTransformsAreAppropriate( true );
388
389 try
390 {
391#ifdef QGISDEBUG
392 // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
393#endif
394 // Split the extent into two if the source CRS is
395 // geographic and the extent crosses the split in
396 // geographic coordinates (usually +/- 180 degrees,
397 // and is assumed to be so here), and draw each
398 // extent separately.
399 static const double SPLIT_COORD = 180.0;
400
401 if ( ml->crs().isGeographic() )
402 {
403 if ( ml->type() == Qgis::LayerType::Vector && !approxTransform.destinationCrs().isGeographic() )
404 {
405 // if we transform from a projected coordinate system check
406 // check if transforming back roughly returns the input
407 // extend - otherwise render the world.
408 QgsRectangle extent1 = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
409 QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, Qgis::TransformDirection::Forward );
410
411 QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
412 .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
413 .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
414 .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
415 .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
416 , 3 );
417
418 // can differ by a maximum of up to 20% of height/width
419 if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
420 && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
421 && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
422 && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
423 )
424 {
425 extent = extent1;
426 }
427 else
428 {
429 extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
430 res = false;
431 }
432 }
433 else
434 {
435 // Note: ll = lower left point
436 QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
438
439 // and ur = upper right point
440 QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
442
443 QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
444
445 extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
446
447 QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
448
449 if ( ll.x() > ur.x() )
450 {
451 // the coordinates projected in reverse order than what one would expect.
452 // we are probably looking at an area that includes longitude of 180 degrees.
453 // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
454 // so let's use (-180,180). This hopefully does not add too much overhead. It is
455 // more straightforward than rendering with two separate extents and more consistent
456 // for rendering, labeling and caching as everything is rendered just in one go
457 extent.setXMinimum( -SPLIT_COORD );
458 extent.setXMaximum( SPLIT_COORD );
459 res = false;
460 }
461 }
462
463 // TODO: the above rule still does not help if using a projection that covers the whole
464 // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
465 // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
466 // but in fact the extent should cover the whole world.
467 }
468 else // can't cross 180
469 {
470 if ( approxTransform.destinationCrs().isGeographic() &&
471 ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
472 extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
473 // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
474 // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
475 // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
476 // but this seems like a safer choice.
477 {
478 extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
479 res = false;
480 }
481 else
482 extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
483 }
484 }
485 catch ( QgsCsException &e )
486 {
487 QgsDebugError( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
488 extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
489 r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
490 res = false;
491 }
492
493 return res;
494}
495
496QImage *QgsMapRendererJob::allocateImage( QString layerId )
497{
498 QImage *image = new QImage( mSettings.deviceOutputSize(),
499 mSettings.outputImageFormat() );
500 image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
501 image->setDotsPerMeterX( 1000 * mSettings.outputDpi() / 25.4 );
502 image->setDotsPerMeterY( 1000 * mSettings.outputDpi() / 25.4 );
503 if ( image->isNull() )
504 {
505 mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
506 delete image;
507 return nullptr;
508 }
509 return image;
510}
511
512QgsElevationMap *QgsMapRendererJob::allocateElevationMap( QString layerId )
513{
514 auto elevationMap = std::make_unique<QgsElevationMap>( mSettings.deviceOutputSize(), mSettings.devicePixelRatio() );
515 if ( !elevationMap->isValid() )
516 {
517 mErrors.append( Error( layerId, tr( "Insufficient memory for elevation map %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
518 return nullptr;
519 }
520 return elevationMap.release();
521}
522
523QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image, const QgsRenderContext *context )
524{
525 QPainter *painter = nullptr;
526 image = allocateImage( layerId );
527 if ( image )
528 {
529 painter = new QPainter( image );
530 context->setPainterFlagsUsingContext( painter );
531 }
532 return painter;
533}
534
535QgsMapRendererJob::PictureAndPainter QgsMapRendererJob::allocatePictureAndPainter( const QgsRenderContext *context )
536{
537 auto picture = std::make_unique<QPicture>();
538 QPainter *painter = new QPainter( picture.get() );
539 context->setPainterFlagsUsingContext( painter );
540 return { std::move( picture ), painter };
541}
542
543std::vector<LayerRenderJob> QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
544{
545 std::vector< LayerRenderJob > layerJobs;
546
547 // render all layers in the stack, starting at the base
548 QListIterator<QgsMapLayer *> li( mSettings.layers() );
549 li.toBack();
550
551 if ( mCache )
552 {
553 bool cacheValid = mCache->updateParameters( mSettings.visibleExtent(), mSettings.mapToPixel() );
554 Q_UNUSED( cacheValid )
555 QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
556 }
557
558 bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
559
560 while ( li.hasPrevious() )
561 {
562 QgsMapLayer *ml = li.previous();
563
564 QgsDebugMsgLevel( QStringLiteral( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
565 .arg( ml->name() )
566 .arg( ml->minimumScale() )
567 .arg( ml->maximumScale() )
568 .arg( ml->hasScaleBasedVisibility() )
569 .arg( ml->blendMode() )
570 .arg( ml->isValid() )
571 , 3 );
572
573 if ( !ml->isValid() )
574 {
575 QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
576 mErrors.append( Error( ml->id(), QString( "Layer %1 is invalid, skipped" ).arg( ml->name() ) ) );
577 continue;
578 }
579
580 if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
581 {
582 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
583 continue;
584 }
585
586 if ( mSettings.isTemporal() && ml->temporalProperties() && !ml->temporalProperties()->isVisibleInTemporalRange( mSettings.temporalRange() ) )
587 {
588 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
589 continue;
590 }
591
592 if ( !mSettings.zRange().isInfinite() && ml->elevationProperties() && !ml->elevationProperties()->isVisibleInZRange( mSettings.zRange(), ml ) )
593 {
594 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
595 continue;
596 }
597
598 QgsRectangle r1 = mSettings.visibleExtent(), r2;
599 r1.grow( mSettings.extentBuffer() );
600 QgsCoordinateTransform ct;
601
602 ct = mSettings.layerTransform( ml );
603 bool haveExtentInLayerCrs = true;
604 if ( ct.isValid() )
605 {
606 haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
607 }
608 QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
609 if ( !r1.isFinite() || !r2.isFinite() )
610 {
611 mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
612 continue;
613 }
614
615 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
616
617 // Force render of layers that are being edited
618 // or if there's a labeling engine that needs the layer to register features
619 if ( mCache )
620 {
621 const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
622 if ( ( vl && vl->isEditable() ) || requiresLabeling )
623 {
624 mCache->clearCacheImage( ml->id() );
625 }
626 }
627
628 layerJobs.emplace_back( LayerRenderJob() );
629 LayerRenderJob &job = layerJobs.back();
630 job.layer = ml;
631 job.layerId = ml->id();
632 job.renderAboveLabels = ml->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool();
633 job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
634
635 job.setContext( std::make_unique< QgsRenderContext >( QgsRenderContext::fromMapSettings( mSettings ) ) );
636 if ( !ml->customProperty( QStringLiteral( "_noset_layer_expression_context" ) ).toBool() )
637 job.context()->expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
638 job.context()->setPainter( painter );
639 job.context()->setLabelingEngine( labelingEngine2 );
640 job.context()->setLabelSink( labelSink() );
641 job.context()->setCoordinateTransform( ct );
642 job.context()->setExtent( r1 );
643
644 // Also check geographic, see: https://github.com/qgis/QGIS/issues/45200
645 if ( !haveExtentInLayerCrs || ( ct.isValid() && ( ct.sourceCrs().isGeographic() != ct.destinationCrs().isGeographic() ) ) )
646 job.context()->setFlag( Qgis::RenderContextFlag::ApplyClipAfterReprojection, true );
647
648 if ( mFeatureFilterProvider )
649 {
650 job.context()->setFeatureFilterProvider( mFeatureFilterProvider );
651 }
652
653 QgsMapLayerStyleOverride styleOverride( ml );
654 if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
655 styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
656
657 job.blendMode = ml->blendMode();
658
659 if ( ml->type() == Qgis::LayerType::Raster )
660 {
661 // raster layers are abnormal wrt opacity handling -- opacity is sometimes handled directly within the raster layer renderer
662 QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( ml );
664 {
665 job.opacity = 1.0;
666 }
667 else
668 {
669 job.opacity = ml->opacity();
670 }
671 }
672 else
673 {
674 job.opacity = ml->opacity();
675 }
676
677 const QgsElevationShadingRenderer shadingRenderer = mSettings.elevationShadingRenderer();
678
679 // if we can use the cache, let's do it and avoid rendering!
680 const bool canUseCache = mSettings.rasterizedRenderingPolicy() == Qgis::RasterizedRenderingPolicy::Default
681 && mCache;
682 if ( canUseCache && mCache->hasCacheImage( ml->id() ) )
683 {
684 job.cached = true;
685 job.imageInitialized = true;
686 job.img = new QImage( mCache->cacheImage( ml->id() ) );
687 if ( shadingRenderer.isActive() &&
688 ml->elevationProperties() &&
690 mCache->hasCacheImage( ELEVATION_MAP_CACHE_PREFIX + ml->id() ) )
691 job.elevationMap = new QgsElevationMap( mCache->cacheImage( ELEVATION_MAP_CACHE_PREFIX + ml->id() ) );
692 job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
693 job.renderer = nullptr;
694 job.context()->setPainter( nullptr );
695 mLayersRedrawnFromCache.append( ml->id() );
696 continue;
697 }
698
699 QElapsedTimer layerTime;
700 layerTime.start();
701 job.renderer = ml->createMapRenderer( *( job.context() ) );
702 if ( job.renderer )
703 {
704 job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
705 job.context()->setFeedback( job.renderer->feedback() );
706
707 if ( job.renderer->flags().testFlag( Qgis::MapLayerRendererFlag::AffectsLabeling ) )
708 {
709 mAdditionalLabelLayers.append( ml );
710 }
711 }
712
713 // If we are drawing with an alternative blending mode then we need to render to a separate image
714 // before compositing this on the map. This effectively flattens the layer and prevents
715 // blending occurring between objects on the layer
716 if ( canUseCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
717 {
718 // Flattened image for drawing when a blending mode is set
719 job.context()->setPainter( allocateImageAndPainter( ml->id(), job.img, job.context() ) );
720 if ( ! job.img )
721 {
722 delete job.renderer;
723 job.renderer = nullptr;
724 layerJobs.pop_back();
725 continue;
726 }
727 }
728
729 if ( shadingRenderer.isActive()
730 && ml->elevationProperties()
732 {
733 job.elevationMap = allocateElevationMap( ml->id() );
734 job.context()->setElevationMap( job.elevationMap );
735 }
736
737 if ( job.renderer && ( job.renderer->flags() & Qgis::MapLayerRendererFlag::RenderPartialOutputs ) && ( mSettings.flags() & Qgis::MapSettingsFlag::RenderPartialOutput ) )
738 {
739 if ( canUseCache && ( job.renderer->flags() & Qgis::MapLayerRendererFlag::RenderPartialOutputOverPreviousCachedImage ) && mCache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
740 {
741 const QImage cachedImage = mCache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), mSettings.mapToPixel() );
742 if ( !cachedImage.isNull() )
743 {
744 job.previewRenderImage = new QImage( cachedImage );
745 job.previewRenderImageInitialized = true;
746 job.context()->setPreviewRenderPainter( new QPainter( job.previewRenderImage ) );
747 job.context()->setPainterFlagsUsingContext( painter );
748 }
749 }
750 if ( !job.previewRenderImage )
751 {
752 job.context()->setPreviewRenderPainter( allocateImageAndPainter( ml->id(), job.previewRenderImage, job.context() ) );
753 job.previewRenderImageInitialized = false;
754 }
755
756 if ( !job.previewRenderImage )
757 {
758 delete job.context()->previewRenderPainter();
759 job.context()->setPreviewRenderPainter( nullptr );
760 }
761 }
762
763 job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
764 }
765
766 return layerJobs;
767}
768
769std::vector< LayerRenderJob > QgsMapRendererJob::prepareSecondPassJobs( std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob )
770{
771 std::vector< LayerRenderJob > secondPassJobs;
772
773 // We will need to quickly access the associated rendering job of a layer
774 QHash<QString, LayerRenderJob *> layerJobMapping;
775
776 // ... and layer that contains a mask (and whether there is effects implied or not)
777 QMap<QString, bool> maskLayerHasEffects;
778 QMap<int, bool> labelHasEffects;
779
780 struct MaskSource
781 {
782 QString layerId;
783 QString labelRuleId;
784 int labelMaskId;
785 bool hasEffects;
786 MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_, bool hasEffects_ ):
787 layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ), hasEffects( hasEffects_ ) {}
788 };
789
790 // We collect for each layer, the set of symbol layers that will be "masked"
791 // and the list of source layers that have a mask
792 QHash<QString, QPair<QSet<QString>, QList<MaskSource>>> maskedSymbolLayers;
793
796
797 // First up, create a mapping of layer id to jobs. We need this to filter out any masking
798 // which refers to layers which we aren't rendering as part of this map render
799 for ( LayerRenderJob &job : firstPassJobs )
800 {
801 layerJobMapping[job.layerId] = &job;
802 }
803
804 // next, collate a master list of masked layers, skipping over any which refer to layers
805 // which don't have a corresponding render job
806 for ( LayerRenderJob &job : firstPassJobs )
807 {
808 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
809 if ( ! vl )
810 continue;
811
812 // lambda function to factor code for both label masks and symbol layer masks
813 auto collectMasks = [&]( QgsMaskedLayers * masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
814 {
815 bool hasEffects = false;
816 for ( auto it = masks->begin(); it != masks->end(); ++it )
817 {
818 auto lit = maskedSymbolLayers.find( it.key() );
819 if ( lit == maskedSymbolLayers.end() )
820 {
821 maskedSymbolLayers[it.key()] = qMakePair( it.value().symbolLayerIds, QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId, it.value().hasEffects ) );
822 }
823 else
824 {
825 if ( lit->first != it.value().symbolLayerIds )
826 {
827 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() ) );
828 continue;
829 }
830 lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId, hasEffects ) );
831 }
832 hasEffects |= it.value().hasEffects;
833 }
834 if ( ! masks->isEmpty() && labelMaskId == -1 )
835 maskLayerHasEffects[ sourceLayerId ] = hasEffects;
836 };
837
838 // collect label masks
839 QHash<QString, QgsMaskedLayers> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
840 for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
841 {
842 QString labelRule = it.key();
843 // this is a hash of layer id to masks
844 QgsMaskedLayers masks = it.value();
845
846 // filter out masks to those which we are actually rendering
847 QgsMaskedLayers usableMasks;
848 for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
849 {
850 const QString sourceLayerId = mit.key();
851 // if we aren't rendering the source layer as part of this render, we can't process this mask
852 if ( !layerJobMapping.contains( sourceLayerId ) )
853 continue;
854 else
855 usableMasks.insert( sourceLayerId, mit.value() );
856 }
857
858 if ( usableMasks.empty() )
859 continue;
860
861 // group layers by QSet<QgsSymbolLayerReference>
862 QSet<QgsSymbolLayerReference> slRefs;
863 bool hasEffects = false;
864 for ( auto mit = usableMasks.begin(); mit != usableMasks.end(); mit++ )
865 {
866 const QString sourceLayerId = mit.key();
867 // if we aren't rendering the source layer as part of this render, we can't process this mask
868 if ( !layerJobMapping.contains( sourceLayerId ) )
869 continue;
870
871 for ( const QString &symbolLayerId : mit.value().symbolLayerIds )
872 slRefs.insert( QgsSymbolLayerReference( sourceLayerId, symbolLayerId ) );
873
874 hasEffects |= mit.value().hasEffects;
875 }
876 // generate a new mask id for this set
877 int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
878 labelHasEffects[ labelMaskId ] = hasEffects;
879
880 // now collect masks
881 collectMasks( &usableMasks, vl->id(), labelRule, labelMaskId );
882 }
883
884 // collect symbol layer masks
886 collectMasks( &symbolLayerMasks, vl->id() );
887 }
888
889 if ( maskedSymbolLayers.isEmpty() )
890 return secondPassJobs;
891
892 // Prepare label mask images
893 for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
894 {
895 QPaintDevice *maskPaintDevice = nullptr;
896 QPainter *maskPainter = nullptr;
897 if ( forceVector && !labelHasEffects[ maskId ] )
898 {
899 // set a painter to get all masking instruction in order to later clip masked symbol layer
900 auto geomPaintDevice = std::make_unique< QgsGeometryPaintDevice >( true );
901 geomPaintDevice->setStrokedPathSegments( 4 );
902 geomPaintDevice->setSimplificationTolerance( labelJob.context.maskSettings().simplifyTolerance() );
903 maskPaintDevice = geomPaintDevice.release();
904 maskPainter = new QPainter( maskPaintDevice );
905 }
906 else
907 {
908 // Note: we only need an alpha channel here, rather than a full RGBA image
909 QImage *maskImage = nullptr;
910 maskPainter = allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage, &labelJob.context );
911 maskImage->fill( 0 );
912 maskPaintDevice = maskImage;
913 }
914
915 labelJob.context.setMaskPainter( maskPainter, maskId );
916 labelJob.maskPainters.push_back( std::unique_ptr<QPainter>( maskPainter ) );
917 labelJob.maskPaintDevices.push_back( std::unique_ptr<QPaintDevice>( maskPaintDevice ) );
918 }
919 labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
920
921 const bool hasNonDefaultComposition = labelingHasNonDefaultCompositionModes();
922
923 // Prepare second pass jobs
924 // - For raster rendering or vector rendering if effects are involved
925 // 1st pass, 2nd pass and mask are rendered in QImage and composed in composeSecondPass
926 // - For vector rendering if no effects are involved
927 // 1st pass is rendered in QImage, clip paths are generated according to mask and used during
928 // masked symbol layer rendering during second pass, which is rendered in QPicture, second
929 // pass job picture
930
931 // Allocate an image or picture for labels, as suitable.
932 // If we have some non-default label composition modes, we CAN'T render to an image as that
933 // "flattens" composition modes and prevents them interacting with underlying layers.
934 const bool canUseImage = !forceVector && !hasNonDefaultComposition;
935 if ( !labelJob.img && canUseImage )
936 {
937 labelJob.img = allocateImage( QStringLiteral( "labels" ) );
938 }
939 else if ( !labelJob.picture && !canUseImage )
940 {
941 labelJob.picture = std::make_unique<QPicture>( );
942 }
943
944 // first we initialize painter and mask painter for all jobs
945 for ( LayerRenderJob &job : firstPassJobs )
946 {
947 job.maskRequiresLayerRasterization = false;
948
949 auto it = maskedSymbolLayers.find( job.layerId );
950 if ( it != maskedSymbolLayers.end() )
951 {
952 const QList<MaskSource> &sourceList = it->second;
953 for ( const MaskSource &source : sourceList )
954 {
955 job.maskRequiresLayerRasterization |= source.hasEffects;
956 }
957 }
958
959 // update first pass job painter and device if needed
960 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization || ( job.renderer && job.renderer->forceRasterRender() );
961 if ( isRasterRendering && !job.img )
962 {
963 job.context()->setPainter( allocateImageAndPainter( job.layerId, job.img, job.context() ) );
964 }
965 else if ( !isRasterRendering && !job.picture )
966 {
967 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job.context() );
968 job.picture = std::move( pictureAndPainter.first );
969 if ( job.context()->painter()->hasClipping() )
970 {
971 // need to copy clipping paths from original painter, so that e.g. the layout map bounds clipping path is respected
972 pictureAndPainter.second->setClipping( true );
973 pictureAndPainter.second->setClipPath( job.context()->painter()->clipPath() );
974 }
975 job.context()->setPainter( pictureAndPainter.second );
976 // force recreation of layer renderer so it initialize correctly the renderer
977 // especially the RasterLayerRender that need logicalDpiX from painting device
978 job.renderer = job.layer->createMapRenderer( *( job.context() ) );
979 }
980
981 // for layer that mask, generate mask in first pass job
982 if ( maskLayerHasEffects.contains( job.layerId ) )
983 {
984 QPaintDevice *maskPaintDevice = nullptr;
985 QPainter *maskPainter = nullptr;
986 if ( forceVector && !maskLayerHasEffects[ job.layerId ] )
987 {
988 // set a painter to get all masking instruction in order to later clip masked symbol layer
989 auto geomPaintDevice = std::make_unique< QgsGeometryPaintDevice >( );
990 geomPaintDevice->setStrokedPathSegments( 4 );
991 geomPaintDevice->setSimplificationTolerance( job.context()->maskSettings().simplifyTolerance() );
992 maskPaintDevice = geomPaintDevice.release();
993 maskPainter = new QPainter( maskPaintDevice );
994 }
995 else
996 {
997 // Note: we only need an alpha channel here, rather than a full RGBA image
998 QImage *maskImage = nullptr;
999 maskPainter = allocateImageAndPainter( job.layerId, maskImage, job.context() );
1000 maskImage->fill( 0 );
1001 maskPaintDevice = maskImage;
1002 }
1003
1004 job.context()->setMaskPainter( maskPainter );
1005 job.maskPainter.reset( maskPainter );
1006 job.maskPaintDevice.reset( maskPaintDevice );
1007 }
1008 }
1009
1010 for ( LayerRenderJob &job : firstPassJobs )
1011 {
1012 QgsMapLayer *ml = job.layer;
1013
1014 auto it = maskedSymbolLayers.find( job.layerId );
1015 if ( it == maskedSymbolLayers.end() )
1016 continue;
1017
1018 QList<MaskSource> &sourceList = it->second;
1019 const QSet<QString> symbolList = it->first;
1020
1021 secondPassJobs.emplace_back( LayerRenderJob() );
1022 LayerRenderJob &job2 = secondPassJobs.back();
1023
1024 job2.maskRequiresLayerRasterization = job.maskRequiresLayerRasterization;
1025
1026 // Points to the masking jobs. This will be needed during the second pass composition.
1027 for ( MaskSource &source : sourceList )
1028 {
1029 if ( source.labelMaskId != -1 )
1030 job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
1031 else
1032 job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
1033 }
1034
1035 // copy the context from the initial job
1036 job2.setContext( std::make_unique< QgsRenderContext >( *job.context() ) );
1037 // also assign layer to match initial job
1038 job2.layer = job.layer;
1039 job2.renderAboveLabels = job.renderAboveLabels;
1040 job2.layerId = job.layerId;
1041
1042 // associate first pass job with second pass job
1043 job2.firstPassJob = &job;
1044
1045 if ( !forceVector || job2.maskRequiresLayerRasterization )
1046 {
1047 job2.context()->setPainter( allocateImageAndPainter( job.layerId, job2.img, job2.context() ) );
1048 }
1049 else
1050 {
1051 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job2.context() );
1052 if ( job.context()->painter()->hasClipping() )
1053 {
1054 // need to copy clipping paths from original painter, so that e.g. the layout map bounds clipping path is respected
1055 pictureAndPainter.second->setClipping( true );
1056 pictureAndPainter.second->setClipPath( job.context()->painter()->clipPath() );
1057 }
1058 job2.picture = std::move( pictureAndPainter.first );
1059 job2.context()->setPainter( pictureAndPainter.second );
1060 }
1061
1062 if ( ! job2.img && ! job2.picture )
1063 {
1064 secondPassJobs.pop_back();
1065 continue;
1066 }
1067
1068 // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
1069 // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
1070 QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( ml->createMapRenderer( *job2.context() ) );
1071 job2.renderer = mapRenderer;
1072 if ( job2.renderer )
1073 {
1074 job2.context()->setFeedback( job2.renderer->feedback() );
1075 }
1076
1077 // Render only the non masked symbol layer and we will compose 2nd pass with mask and first pass rendering in composeSecondPass
1078 // If vector output is enabled, disabled symbol layers would be actually rendered and masked with clipping path set in QgsMapRendererJob::initSecondPassJobs
1079 job2.context()->setDisabledSymbolLayersV2( symbolList );
1080 }
1081
1082 return secondPassJobs;
1083}
1084
1085QList<QPointer<QgsMapLayer> > QgsMapRendererJob::participatingLabelLayers( QgsLabelingEngine *engine )
1086{
1087 QList<QPointer<QgsMapLayer> > res = _qgis_listRawToQPointer( engine->participatingLayers() );
1088
1089 for ( auto it : std::as_const( mAdditionalLabelLayers ) )
1090 {
1091 if ( !res.contains( it ) )
1092 res.append( it );
1093 }
1094
1095 return res;
1096}
1097
1098void QgsMapRendererJob::initSecondPassJobs( std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob ) const
1099{
1101 return;
1102
1103 for ( LayerRenderJob &job : secondPassJobs )
1104 {
1105 if ( job.maskRequiresLayerRasterization )
1106 continue;
1107
1108 // we draw disabled symbol layer but me mask them with clipping path produced during first pass job
1109 // Resulting 2nd pass job picture will be the final rendering
1110
1111 for ( const QPair<LayerRenderJob *, int> &p : std::as_const( job.maskJobs ) )
1112 {
1113 QPainter *maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[p.second].get();
1114
1115 const QSet<QString> layers = job.context()->disabledSymbolLayersV2();
1116 if ( QgsGeometryPaintDevice *geometryDevice = dynamic_cast<QgsGeometryPaintDevice *>( maskPainter->device() ) )
1117 {
1118 QgsGeometry geometry( geometryDevice->geometry().clone() );
1119
1120#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<10
1121 // structure would be better, but too old GEOS
1122 geometry = geometry.makeValid( Qgis::MakeValidMethod::Linework );
1123#else
1124 geometry = geometry.makeValid( Qgis::MakeValidMethod::Structure );
1125#endif
1126
1127 for ( const QString &symbolLayerId : layers )
1128 {
1129 job.context()->addSymbolLayerClipGeometry( symbolLayerId, geometry );
1130 }
1131 }
1132 }
1133
1134 job.context()->setDisabledSymbolLayersV2( QSet<QString>() );
1135 }
1136}
1137
1138LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
1139{
1140 LabelRenderJob job;
1142 job.context.setPainter( painter );
1143 job.context.setLabelingEngine( labelingEngine2 );
1144 job.context.setFeedback( mLabelingEngineFeedback );
1145 if ( labelingEngine2 )
1146 job.context.labelingEngine()->prepare( job.context );
1147
1148 QgsRectangle r1 = mSettings.visibleExtent();
1149 r1.grow( mSettings.extentBuffer() );
1150 job.context.setExtent( r1 );
1151
1152 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
1153 QgsCoordinateTransform ct;
1154 ct.setDestinationCrs( mSettings.destinationCrs() );
1155 job.context.setCoordinateTransform( ct );
1156
1157 // no cache, no image allocation
1158 if ( mSettings.rasterizedRenderingPolicy() != Qgis::RasterizedRenderingPolicy::Default )
1159 return job;
1160
1161 // if we can use the cache, let's do it and avoid rendering!
1162 bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
1163 if ( hasCache )
1164 {
1165 job.cached = true;
1166 job.complete = true;
1167 job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
1168 Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
1169 job.context.setPainter( nullptr );
1170 }
1171 else
1172 {
1173 if ( canUseLabelCache && ( mCache || !painter ) )
1174 {
1175 job.img = allocateImage( QStringLiteral( "labels" ) );
1176 }
1177 }
1178
1179 return job;
1180}
1181
1182
1183void QgsMapRendererJob::cleanupJobs( std::vector<LayerRenderJob> &jobs )
1184{
1185 for ( LayerRenderJob &job : jobs )
1186 {
1187 if ( job.img )
1188 {
1189 delete job.context()->painter();
1190 job.context()->setPainter( nullptr );
1191
1192 if ( mCache && !job.cached && job.completed && job.layer )
1193 {
1194 QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
1195 mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
1196 mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
1197 }
1198
1199 delete job.img;
1200 job.img = nullptr;
1201 }
1202
1203 if ( job.previewRenderImage )
1204 {
1205 delete job.context()->previewRenderPainter();
1206 job.context()->setPreviewRenderPainter( nullptr );
1207 delete job.previewRenderImage;
1208 job.previewRenderImage = nullptr;
1209 }
1210
1211 if ( job.elevationMap )
1212 {
1213 job.context()->setElevationMap( nullptr );
1214 if ( mCache && !job.cached && job.completed && job.layer )
1215 {
1216 QgsDebugMsgLevel( QStringLiteral( "caching elevation map for %1" ).arg( job.layerId ), 2 );
1217 mCache->setCacheImageWithParameters(
1218 ELEVATION_MAP_CACHE_PREFIX + job.layerId,
1219 job.elevationMap->rawElevationImage(),
1220 mSettings.visibleExtent(),
1221 mSettings.mapToPixel(),
1222 QList< QgsMapLayer * >() << job.layer );
1223 mCache->setCacheImageWithParameters(
1224 ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ),
1225 job.elevationMap->rawElevationImage(),
1226 mSettings.visibleExtent(),
1227 mSettings.mapToPixel(),
1228 QList< QgsMapLayer * >() << job.layer );
1229 }
1230
1231 delete job.elevationMap;
1232 job.elevationMap = nullptr;
1233 }
1234
1235 if ( job.picture )
1236 {
1237 delete job.context()->painter();
1238 job.context()->setPainter( nullptr );
1239 job.picture.reset( nullptr );
1240 }
1241
1242 if ( job.renderer )
1243 {
1244 const QStringList errors = job.renderer->errors();
1245 for ( const QString &message : errors )
1246 mErrors.append( Error( job.renderer->layerId(), message ) );
1247
1248 mRenderedItemResults->appendResults( job.renderer->takeRenderedItemDetails(), *job.context() );
1249
1250 delete job.renderer;
1251 job.renderer = nullptr;
1252 }
1253
1254 if ( job.layer )
1255 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1256
1257 job.maskPainter.reset( nullptr );
1258 job.maskPaintDevice.reset( nullptr );
1259 }
1260
1261 jobs.clear();
1262}
1263
1264void QgsMapRendererJob::cleanupSecondPassJobs( std::vector< LayerRenderJob > &jobs )
1265{
1266 for ( LayerRenderJob &job : jobs )
1267 {
1268 if ( job.img )
1269 {
1270 delete job.context()->painter();
1271 job.context()->setPainter( nullptr );
1272
1273 delete job.img;
1274 job.img = nullptr;
1275 }
1276
1277 if ( job.previewRenderImage )
1278 {
1279 delete job.context()->previewRenderPainter();
1280 job.context()->setPreviewRenderPainter( nullptr );
1281 delete job.previewRenderImage;
1282 job.previewRenderImage = nullptr;
1283 }
1284
1285 if ( job.picture )
1286 {
1287 delete job.context()->painter();
1288 job.context()->setPainter( nullptr );
1289 }
1290
1291 if ( job.renderer )
1292 {
1293 delete job.renderer;
1294 job.renderer = nullptr;
1295 }
1296
1297 if ( job.layer )
1298 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1299 }
1300
1301 jobs.clear();
1302}
1303
1304void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
1305{
1306 if ( job.img )
1307 {
1308 if ( mCache && !job.cached && !job.context.renderingStopped() )
1309 {
1310 QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
1311 mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1312 mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1313 }
1314
1315 delete job.img;
1316 job.img = nullptr;
1317 }
1318
1319 job.picture.reset( nullptr );
1320 job.maskPainters.clear();
1321 job.maskPaintDevices.clear();
1322}
1323
1324
1325#define DEBUG_RENDERING 0
1326
1327QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings,
1328 const std::vector<LayerRenderJob> &jobs,
1329 const LabelRenderJob &labelJob,
1330 const QgsMapRendererCache *cache
1331 )
1332{
1333 QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
1334 image.setDevicePixelRatio( settings.devicePixelRatio() );
1335 image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
1336 image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
1337 image.fill( settings.backgroundColor().rgba() );
1338
1339 const QgsElevationShadingRenderer mapShadingRenderer = settings.elevationShadingRenderer();
1340 std::unique_ptr<QgsElevationMap> mainElevationMap;
1341 if ( mapShadingRenderer.isActive() )
1342 mainElevationMap = std::make_unique<QgsElevationMap>( settings.deviceOutputSize(), settings.devicePixelRatio() );
1343
1344 QPainter painter( &image );
1345
1346#if DEBUG_RENDERING
1347 int i = 0;
1348#endif
1349 for ( const LayerRenderJob &job : jobs )
1350 {
1351 if ( job.renderAboveLabels )
1352 continue; // skip layer for now, it will be rendered after labels
1353
1354 QImage img = layerImageToBeComposed( settings, job, cache );
1355 if ( img.isNull() )
1356 continue; // image is not prepared and not even in cache
1357
1358 painter.setCompositionMode( job.blendMode );
1359 painter.setOpacity( job.opacity );
1360
1361 if ( mainElevationMap )
1362 {
1363 QgsElevationMap layerElevationMap = layerElevationToBeComposed( settings, job, cache );
1364 if ( layerElevationMap.isValid() )
1365 mainElevationMap->combine( layerElevationMap, mapShadingRenderer.combinedElevationMethod() );
1366 }
1367
1368
1369#if DEBUG_RENDERING
1370 img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
1371 i++;
1372#endif
1373
1374 painter.drawImage( 0, 0, img );
1375 }
1376
1377 if ( mapShadingRenderer.isActive() && mainElevationMap )
1378 {
1379 mapShadingRenderer.renderShading( *mainElevationMap.get(), image, QgsRenderContext::fromMapSettings( settings ) );
1380 }
1381
1382 // IMPORTANT - don't draw labelJob img before the label job is complete,
1383 // as the image is uninitialized and full of garbage before the label job
1384 // commences
1385 if ( labelJob.img && labelJob.complete )
1386 {
1387 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1388 painter.setOpacity( 1.0 );
1389 painter.drawImage( 0, 0, *labelJob.img );
1390 }
1391 else if ( labelJob.picture && labelJob.complete )
1392 {
1393 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1394 painter.setOpacity( 1.0 );
1395 QgsPainting::drawPicture( &painter, QPointF( 0, 0 ), *labelJob.picture );
1396 }
1397 // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
1398 // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
1399 // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
1400 else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
1401 {
1402 const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
1403 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1404 painter.setOpacity( 1.0 );
1405 painter.drawImage( 0, 0, labelCacheImage );
1406 }
1407
1408 // render any layers with the renderAboveLabels flag now
1409 for ( const LayerRenderJob &job : jobs )
1410 {
1411 if ( !job.renderAboveLabels )
1412 continue;
1413
1414 QImage img = layerImageToBeComposed( settings, job, cache );
1415 if ( img.isNull() )
1416 continue; // image is not prepared and not even in cache
1417
1418 painter.setCompositionMode( job.blendMode );
1419 painter.setOpacity( job.opacity );
1420
1421 painter.drawImage( 0, 0, img );
1422 }
1423
1424 painter.end();
1425#if DEBUG_RENDERING
1426 image.save( "/tmp/final.png" );
1427#endif
1428 return image;
1429}
1430
1432 const QgsMapSettings &settings,
1433 const LayerRenderJob &job,
1434 const QgsMapRendererCache *cache
1435)
1436{
1437 if ( job.imageCanBeComposed() )
1438 {
1439 if ( job.previewRenderImage && !job.completed )
1440 return *job.previewRenderImage;
1441
1442 Q_ASSERT( job.img );
1443 return *job.img;
1444 }
1445 else
1446 {
1447 if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
1448 {
1449 return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
1450 }
1451 else
1452 return QImage();
1453 }
1454}
1455
1456QgsElevationMap QgsMapRendererJob::layerElevationToBeComposed( const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache )
1457{
1458 if ( job.imageCanBeComposed() && job.elevationMap )
1459 {
1460 return *job.elevationMap;
1461 }
1462 else
1463 {
1464 if ( cache && cache->hasAnyCacheImage( ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ) ) )
1465 return QgsElevationMap( cache->transformedCacheImage( ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() ) );
1466 else
1467 return QgsElevationMap();
1468 }
1469}
1470
1471void QgsMapRendererJob::composeSecondPass( std::vector<LayerRenderJob> &secondPassJobs, LabelRenderJob &labelJob, bool forceVector )
1472{
1473 // compose the second pass with the mask
1474 for ( LayerRenderJob &job : secondPassJobs )
1475 {
1476 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization;
1477
1478 // Merge all mask images into the first one if we have more than one mask image
1479 if ( isRasterRendering && job.maskJobs.size() > 1 )
1480 {
1481 QPainter *maskPainter = nullptr;
1482 for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
1483 {
1484 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1485 if ( !maskPainter )
1486 {
1487 maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[ p.second ].get();
1488 }
1489 else
1490 {
1491 maskPainter->drawImage( 0, 0, *maskImage );
1492 }
1493 }
1494 }
1495
1496 if ( ! job.maskJobs.isEmpty() )
1497 {
1498 // All have been merged into the first
1499 QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
1500 if ( isRasterRendering )
1501 {
1502 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1503
1504 // Only retain parts of the second rendering that are "inside" the mask image
1505 QPainter *painter = job.context()->painter();
1506
1507 painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1508
1509 //Create an "alpha binarized" image of the maskImage to :
1510 //* Eliminate antialiasing artifact
1511 //* Avoid applying mask opacity to elements under the mask but not masked
1512 QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
1513 QVector<QRgb> mswTable;
1514 mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
1515 mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
1516 maskBinAlpha.setColorTable( mswTable );
1517 painter->drawImage( 0, 0, maskBinAlpha );
1518
1519 // Modify the first pass' image ...
1520 {
1521 QPainter tempPainter;
1522
1523 // reuse the first pass painter, if available
1524 QPainter *painter1 = job.firstPassJob->context()->painter();
1525 if ( ! painter1 )
1526 {
1527 tempPainter.begin( job.firstPassJob->img );
1528 painter1 = &tempPainter;
1529 }
1530
1531 // ... first retain parts that are "outside" the mask image
1532 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
1533 painter1->drawImage( 0, 0, *maskImage );
1534
1535 // ... and overpaint the second pass' image on it
1536 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
1537 painter1->drawImage( 0, 0, *job.img );
1538 }
1539 }
1540 else
1541 {
1542 job.firstPassJob->picture = std::move( job.picture );
1543 job.picture = nullptr;
1544 }
1545 }
1546 }
1547}
1548
1549void QgsMapRendererJob::logRenderingTime( const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob )
1550{
1551 if ( !settingsLogCanvasRefreshEvent->value() )
1552 return;
1553
1554 QMultiMap<int, QString> elapsed;
1555 for ( const LayerRenderJob &job : jobs )
1556 elapsed.insert( job.renderingTime, job.layerId );
1557 for ( const LayerRenderJob &job : secondPassJobs )
1558 elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
1559
1560 elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
1561
1562 QList<int> tt( elapsed.uniqueKeys() );
1563 std::sort( tt.begin(), tt.end(), std::greater<int>() );
1564 for ( int t : std::as_const( tt ) )
1565 {
1566 QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
1567 }
1568 QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
1569}
1570
1571void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1572{
1573 QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1574
1575 std::unique_ptr< QgsScopedRuntimeProfile > labelingProfile;
1576 if ( renderContext.flags() & Qgis::RenderContextFlag::RecordProfile )
1577 {
1578 labelingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "(labeling)" ), QStringLiteral( "rendering" ) );
1579 }
1580
1581 QElapsedTimer t;
1582 t.start();
1583
1584 // Reset the composition mode before rendering the labels
1585 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1586
1587 renderContext.setPainter( painter );
1588
1589 if ( labelingEngine2 )
1590 {
1591 labelingEngine2->run( renderContext );
1592 }
1593
1594 QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1595}
1596
1597void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1598{
1599 Q_UNUSED( settings )
1600
1601 drawLabeling( renderContext, labelingEngine2, painter );
1602}
1603
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2704
@ InternalLayerOpacityHandling
The renderer internally handles the raster layer's opacity, so the default layer level opacity handli...
Definition qgis.h:1503
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:198
@ Plugin
Plugin based layer.
Definition qgis.h:193
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:199
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:196
@ Vector
Vector layer.
Definition qgis.h:191
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:195
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:194
@ Raster
Raster layer.
Definition qgis.h:192
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:197
@ AffectsLabeling
The layer rendering will interact with the map labeling.
Definition qgis.h:2787
@ RenderPartialOutputOverPreviousCachedImage
When rendering temporary in-progress preview renders, these preview renders can be drawn over any pre...
Definition qgis.h:2786
@ RenderPartialOutputs
The renderer benefits from rendering temporary in-progress preview renders. These are temporary resul...
Definition qgis.h:2785
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
Definition qgis.h:2764
@ RecordProfile
Enable run-time profiling while rendering.
Definition qgis.h:2768
@ Linework
Combines all rings into a set of noded lines and then extracts valid polygons from that linework.
Definition qgis.h:2181
@ Structure
Structured method, first makes all rings valid and then merges shells and subtracts holes from shells...
Definition qgis.h:2182
@ Forward
Forward transform (from source to destination).
Definition qgis.h:2672
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
Definition qgis.h:2719
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
Definition qgis.h:2726
@ ForceRasterMasks
Force symbol masking to be applied using a raster method. This is considerably faster when compared t...
Definition qgis.h:2733
virtual bool hasNonDefaultCompositionMode() const =0
Returns true the labeling requires a non-default composition mode.
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
virtual bool hasNonDefaultCompositionMode() const =0
Returns true the labeling requires a non-default composition mode.
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
virtual bool hasNonDefaultCompositionMode() const =0
Returns true the labeling requires a non-default composition mode.
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
Stores a digital elevation model in a raster image which may get updated as a part of the map layer r...
bool isValid() const
Returns whether the elevation map is valid.
Qgis::ElevationMapCombineMethod combinedElevationMethod() const
Returns the method used when conbining different elevation sources.
bool isActive() const
Returns whether this shading renderer is active.
void renderShading(const QgsElevationMap &elevation, QImage &image, const QgsRenderContext &context) const
Render shading on image condidering the elevation map elevation and the renderer context context If e...
QString what() const
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QgsFeedback subclass for granular reporting of labeling engine progress.
Provides map labeling functionality.
virtual void run(QgsRenderContext &context)=0
Runs the labeling job.
QList< QgsMapLayer * > participatingLayers() const
Returns a list of layers with providers in the engine.
static void warning(const QString &msg)
Goes to qWarning.
virtual bool isVisibleInZRange(const QgsDoubleRange &range, QgsMapLayer *layer=nullptr) const
Returns true if the layer should be visible and rendered for the specified z range.
virtual bool hasElevation() const
Returns true if the layer has an elevation or z component.
virtual bool isVisibleInTemporalRange(const QgsDateTimeRange &range) const
Returns true if the layer should be visible and rendered for the specified time range.
Base class for all map layer types.
Definition qgsmaplayer.h:80
QString name
Definition qgsmaplayer.h:84
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
QString id
Definition qgsmaplayer.h:83
Qgis::LayerType type
Definition qgsmaplayer.h:90
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
double minimumScale() const
Returns the minimum map scale (i.e.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
double opacity
Definition qgsmaplayer.h:92
double maximumScale() const
Returns the maximum map scale (i.e.
Responsible for keeping a cache of rendered images resulting from a map rendering job.
bool hasAnyCacheImage(const QString &cacheKey, double minimumScaleThreshold=0, double maximumScaleThreshold=0) const
Returns true if the cache contains an image with the specified cacheKey with any cache's parameters (...
QImage transformedCacheImage(const QString &cacheKey, const QgsMapToPixel &mtp) const
Returns the cached image for the specified cacheKey transformed to the particular extent and scale.
Abstract base class for map rendering implementations.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
QList< QPointer< QgsMapLayer > > participatingLabelLayers(QgsLabelingEngine *engine)
Returns a list of the layers participating in the map labeling.
static QgsElevationMap layerElevationToBeComposed(const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache)
static QImage composeImage(const QgsMapSettings &settings, const std::vector< LayerRenderJob > &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
void cleanupSecondPassJobs(std::vector< LayerRenderJob > &jobs)
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
void initSecondPassJobs(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob) const
Initialize secondPassJobs according to what have been rendered (mask clipping path e....
static QImage layerImageToBeComposed(const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache)
QHash< QString, int > mLayerRenderingTimeHints
Approximate expected layer rendering time per layer, by layer ID.
std::unique_ptr< QgsRenderedItemResults > mRenderedItemResults
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
static const QString LABEL_PREVIEW_CACHE_ID
QgsMapRendererCache ID string for cached label image during preview compositions only.
QList< QPointer< QgsMapLayer > > mAdditionalLabelLayers
Additional layers participating in labeling problem.
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
QgsMapRendererCache * mCache
void finished()
emitted when asynchronous rendering is finished (or canceled).
bool labelingHasNonDefaultCompositionModes() const
Returns true if any component of the map labeling requires non-default composition modes.
QgsMapSettings mSettings
static const QgsSettingsEntryBool * settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
QgsMapRendererJob(const QgsMapSettings &settings)
~QgsMapRendererJob() override
void start()
Start the rendering job and immediately return.
QStringList mLayersRedrawnFromCache
QStringList layersRedrawnFromCache() const
Returns a list of the layer IDs for all layers which were redrawn from cached images.
QList< QgsMapRendererJob::Error > Errors
static const QString LABEL_CACHE_ID
QgsMapRendererCache ID string for cached label image.
static const QString ELEVATION_MAP_CACHE_PREFIX
QgsMapRendererCache prefix string for cached elevation map image.
QHash< QgsWeakMapLayerPointer, int > mPerLayerRenderingTime
Render time (in ms) per layer, by layer ID.
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
QgsLabelingEngineFeedback * labelingEngineFeedback()
Returns the associated labeling engine feedback object.
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob, bool forceVector=false)
Compose second pass images into first pass images.
std::vector< LayerRenderJob > prepareSecondPassJobs(std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
static const QgsSettingsEntryString * settingsMaskBackend
Settings entry for mask painting backend engine.
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
QgsLabelSink * labelSink() const
Returns the label sink associated to this rendering job.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
QgsMapRendererQImageJob(const QgsMapSettings &settings)
Contains configuration for rendering maps.
QSize deviceOutputSize() const
Returns the device output size of the map render.
QColor backgroundColor() const
Returns the background color of the map.
const QgsMapToPixel & mapToPixel() const
float devicePixelRatio() const
Returns the device pixel ratio.
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
const QgsElevationShadingRenderer & elevationShadingRenderer() const
Returns the shading renderer used to render shading on the entire map.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
const QgsAbstractMeshLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static void drawPicture(QPainter *painter, const QPointF &point, const QPicture &picture)
Draws a picture onto a painter, correctly applying workarounds to avoid issues with incorrect scaling...
static bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
double x
Definition qgspointxy.h:63
const QgsAbstractRasterLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
virtual Qgis::RasterRendererFlags flags() const
Returns flags which dictate renderer behavior.
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double xMinimum
double yMinimum
double xMaximum
void setXMinimum(double x)
Set the minimum x value.
void setXMaximum(double x)
Set the maximum x value.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Contains information about the context of a rendering operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
Stores collated details of rendered items during a map rendering operation.
A boolean settings entry.
A string settings entry.
static QgsSettingsTreeNode * sTreeMap
static QgsMaskedLayers symbolLayerMasks(const QgsVectorLayer *)
Returns all masks that may be defined on symbol layers for a given vector layer.
static QHash< QString, QgsMaskedLayers > labelMasks(const QgsVectorLayer *)
Returns masks defined in labeling options of a layer.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
bool isEditable() const final
Returns true if the provider is in editing mode.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57
QHash< QString, QgsMaskedLayer > QgsMaskedLayers
masked layers where key is the layer id