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