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