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