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