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