QGIS API Documentation 3.99.0-Master (9e5f463f1a9)
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 {
644 job.context()->setFeatureFilterProvider( mFeatureFilterProvider );
645 }
646
647 QgsMapLayerStyleOverride styleOverride( ml );
648 if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
649 styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
650
651 job.blendMode = ml->blendMode();
652
653 if ( ml->type() == Qgis::LayerType::Raster )
654 {
655 // raster layers are abnormal wrt opacity handling -- opacity is sometimes handled directly within the raster layer renderer
656 QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( ml );
658 {
659 job.opacity = 1.0;
660 }
661 else
662 {
663 job.opacity = ml->opacity();
664 }
665 }
666 else
667 {
668 job.opacity = ml->opacity();
669 }
670
672
673 // if we can use the cache, let's do it and avoid rendering!
675 && mCache;
676 if ( canUseCache && mCache->hasCacheImage( ml->id() ) )
677 {
678 job.cached = true;
679 job.imageInitialized = true;
680 job.img = new QImage( mCache->cacheImage( ml->id() ) );
681 if ( shadingRenderer.isActive() &&
682 ml->elevationProperties() &&
685 job.elevationMap = new QgsElevationMap( mCache->cacheImage( ELEVATION_MAP_CACHE_PREFIX + ml->id() ) );
686 job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
687 job.renderer = nullptr;
688 job.context()->setPainter( nullptr );
689 mLayersRedrawnFromCache.append( ml->id() );
690 continue;
691 }
692
693 QElapsedTimer layerTime;
694 layerTime.start();
695 job.renderer = ml->createMapRenderer( *( job.context() ) );
696 if ( job.renderer )
697 {
698 job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
699 job.context()->setFeedback( job.renderer->feedback() );
700
701 if ( job.renderer->flags().testFlag( Qgis::MapLayerRendererFlag::AffectsLabeling ) )
702 {
703 mAdditionalLabelLayers.append( ml );
704 }
705 }
706
707 // If we are drawing with an alternative blending mode then we need to render to a separate image
708 // before compositing this on the map. This effectively flattens the layer and prevents
709 // blending occurring between objects on the layer
710 if ( canUseCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
711 {
712 // Flattened image for drawing when a blending mode is set
713 job.context()->setPainter( allocateImageAndPainter( ml->id(), job.img, job.context() ) );
714 if ( ! job.img )
715 {
716 delete job.renderer;
717 job.renderer = nullptr;
718 layerJobs.pop_back();
719 continue;
720 }
721 }
722
723 if ( shadingRenderer.isActive()
724 && ml->elevationProperties()
726 {
727 job.elevationMap = allocateElevationMap( ml->id() );
728 job.context()->setElevationMap( job.elevationMap );
729 }
730
732 {
733 if ( canUseCache && ( job.renderer->flags() & Qgis::MapLayerRendererFlag::RenderPartialOutputOverPreviousCachedImage ) && mCache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
734 {
735 const QImage cachedImage = mCache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), mSettings.mapToPixel() );
736 if ( !cachedImage.isNull() )
737 {
738 job.previewRenderImage = new QImage( cachedImage );
739 job.previewRenderImageInitialized = true;
740 job.context()->setPreviewRenderPainter( new QPainter( job.previewRenderImage ) );
741 job.context()->setPainterFlagsUsingContext( painter );
742 }
743 }
744 if ( !job.previewRenderImage )
745 {
746 job.context()->setPreviewRenderPainter( allocateImageAndPainter( ml->id(), job.previewRenderImage, job.context() ) );
747 job.previewRenderImageInitialized = false;
748 }
749
750 if ( !job.previewRenderImage )
751 {
752 delete job.context()->previewRenderPainter();
753 job.context()->setPreviewRenderPainter( nullptr );
754 }
755 }
756
757 job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
758 }
759
760 return layerJobs;
761}
762
763std::vector< LayerRenderJob > QgsMapRendererJob::prepareSecondPassJobs( std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob )
764{
765 std::vector< LayerRenderJob > secondPassJobs;
766
767 // We will need to quickly access the associated rendering job of a layer
768 QHash<QString, LayerRenderJob *> layerJobMapping;
769
770 // ... and layer that contains a mask (and whether there is effects implied or not)
771 QMap<QString, bool> maskLayerHasEffects;
772 QMap<int, bool> labelHasEffects;
773
774 struct MaskSource
775 {
776 QString layerId;
777 QString labelRuleId;
778 int labelMaskId;
779 bool hasEffects;
780 MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_, bool hasEffects_ ):
781 layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ), hasEffects( hasEffects_ ) {}
782 };
783
784 // We collect for each layer, the set of symbol layers that will be "masked"
785 // and the list of source layers that have a mask
786 QHash<QString, QPair<QSet<QString>, QList<MaskSource>>> maskedSymbolLayers;
787
790
791 // First up, create a mapping of layer id to jobs. We need this to filter out any masking
792 // which refers to layers which we aren't rendering as part of this map render
793 for ( LayerRenderJob &job : firstPassJobs )
794 {
795 layerJobMapping[job.layerId] = &job;
796 }
797
798 // next, collate a master list of masked layers, skipping over any which refer to layers
799 // which don't have a corresponding render job
800 for ( LayerRenderJob &job : firstPassJobs )
801 {
802 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
803 if ( ! vl )
804 continue;
805
806 // lambda function to factor code for both label masks and symbol layer masks
807 auto collectMasks = [&]( QgsMaskedLayers * masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
808 {
809 bool hasEffects = false;
810 for ( auto it = masks->begin(); it != masks->end(); ++it )
811 {
812 auto lit = maskedSymbolLayers.find( it.key() );
813 if ( lit == maskedSymbolLayers.end() )
814 {
815 maskedSymbolLayers[it.key()] = qMakePair( it.value().symbolLayerIds, QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId, it.value().hasEffects ) );
816 }
817 else
818 {
819 if ( lit->first != it.value().symbolLayerIds )
820 {
821 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() ) );
822 continue;
823 }
824 lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId, hasEffects ) );
825 }
826 hasEffects |= it.value().hasEffects;
827 }
828 if ( ! masks->isEmpty() && labelMaskId == -1 )
829 maskLayerHasEffects[ sourceLayerId ] = hasEffects;
830 };
831
832 // collect label masks
833 QHash<QString, QgsMaskedLayers> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
834 for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
835 {
836 QString labelRule = it.key();
837 // this is a hash of layer id to masks
838 QgsMaskedLayers masks = it.value();
839
840 // filter out masks to those which we are actually rendering
841 QgsMaskedLayers usableMasks;
842 for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
843 {
844 const QString sourceLayerId = mit.key();
845 // if we aren't rendering the source layer as part of this render, we can't process this mask
846 if ( !layerJobMapping.contains( sourceLayerId ) )
847 continue;
848 else
849 usableMasks.insert( sourceLayerId, mit.value() );
850 }
851
852 if ( usableMasks.empty() )
853 continue;
854
855 // group layers by QSet<QgsSymbolLayerReference>
856 QSet<QgsSymbolLayerReference> slRefs;
857 bool hasEffects = false;
858 for ( auto mit = usableMasks.begin(); mit != usableMasks.end(); mit++ )
859 {
860 const QString sourceLayerId = mit.key();
861 // if we aren't rendering the source layer as part of this render, we can't process this mask
862 if ( !layerJobMapping.contains( sourceLayerId ) )
863 continue;
864
865 for ( const QString &symbolLayerId : mit.value().symbolLayerIds )
866 slRefs.insert( QgsSymbolLayerReference( sourceLayerId, symbolLayerId ) );
867
868 hasEffects |= mit.value().hasEffects;
869 }
870 // generate a new mask id for this set
871 int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
872 labelHasEffects[ labelMaskId ] = hasEffects;
873
874 // now collect masks
875 collectMasks( &usableMasks, vl->id(), labelRule, labelMaskId );
876 }
877
878 // collect symbol layer masks
880 collectMasks( &symbolLayerMasks, vl->id() );
881 }
882
883 if ( maskedSymbolLayers.isEmpty() )
884 return secondPassJobs;
885
886 // Prepare label mask images
887 for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
888 {
889 QPaintDevice *maskPaintDevice = nullptr;
890 QPainter *maskPainter = nullptr;
891 if ( forceVector && !labelHasEffects[ maskId ] )
892 {
893 // set a painter to get all masking instruction in order to later clip masked symbol layer
894 auto geomPaintDevice = std::make_unique< QgsGeometryPaintDevice >( true );
895 geomPaintDevice->setStrokedPathSegments( 4 );
896 geomPaintDevice->setSimplificationTolerance( labelJob.context.maskSettings().simplifyTolerance() );
897 maskPaintDevice = geomPaintDevice.release();
898 maskPainter = new QPainter( maskPaintDevice );
899 }
900 else
901 {
902 // Note: we only need an alpha channel here, rather than a full RGBA image
903 QImage *maskImage = nullptr;
904 maskPainter = allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage, &labelJob.context );
905 maskImage->fill( 0 );
906 maskPaintDevice = maskImage;
907 }
908
909 labelJob.context.setMaskPainter( maskPainter, maskId );
910 labelJob.maskPainters.push_back( std::unique_ptr<QPainter>( maskPainter ) );
911 labelJob.maskPaintDevices.push_back( std::unique_ptr<QPaintDevice>( maskPaintDevice ) );
912 }
913 labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
914
915 const bool hasNonDefaultComposition = labelingHasNonDefaultCompositionModes();
916
917 // Prepare second pass jobs
918 // - For raster rendering or vector rendering if effects are involved
919 // 1st pass, 2nd pass and mask are rendered in QImage and composed in composeSecondPass
920 // - For vector rendering if no effects are involved
921 // 1st pass is rendered in QImage, clip paths are generated according to mask and used during
922 // masked symbol layer rendering during second pass, which is rendered in QPicture, second
923 // pass job picture
924
925 // Allocate an image or picture for labels, as suitable.
926 // If we have some non-default label composition modes, we CAN'T render to an image as that
927 // "flattens" composition modes and prevents them interacting with underlying layers.
928 const bool canUseImage = !forceVector && !hasNonDefaultComposition;
929 if ( !labelJob.img && canUseImage )
930 {
931 labelJob.img = allocateImage( QStringLiteral( "labels" ) );
932 }
933 else if ( !labelJob.picture && !canUseImage )
934 {
935 labelJob.picture.reset( new QPicture() );
936 }
937
938 // first we initialize painter and mask painter for all jobs
939 for ( LayerRenderJob &job : firstPassJobs )
940 {
941 job.maskRequiresLayerRasterization = false;
942
943 auto it = maskedSymbolLayers.find( job.layerId );
944 if ( it != maskedSymbolLayers.end() )
945 {
946 const QList<MaskSource> &sourceList = it->second;
947 for ( const MaskSource &source : sourceList )
948 {
949 job.maskRequiresLayerRasterization |= source.hasEffects;
950 }
951 }
952
953 // update first pass job painter and device if needed
954 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization || ( job.renderer && job.renderer->forceRasterRender() );
955 if ( isRasterRendering && !job.img )
956 {
957 job.context()->setPainter( allocateImageAndPainter( job.layerId, job.img, job.context() ) );
958 }
959 else if ( !isRasterRendering && !job.picture )
960 {
961 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job.context() );
962 job.picture = std::move( pictureAndPainter.first );
963 if ( job.context()->painter()->hasClipping() )
964 {
965 // need to copy clipping paths from original painter, so that e.g. the layout map bounds clipping path is respected
966 pictureAndPainter.second->setClipping( true );
967 pictureAndPainter.second->setClipPath( job.context()->painter()->clipPath() );
968 }
969 job.context()->setPainter( pictureAndPainter.second );
970 // force recreation of layer renderer so it initialize correctly the renderer
971 // especially the RasterLayerRender that need logicalDpiX from painting device
972 job.renderer = job.layer->createMapRenderer( *( job.context() ) );
973 }
974
975 // for layer that mask, generate mask in first pass job
976 if ( maskLayerHasEffects.contains( job.layerId ) )
977 {
978 QPaintDevice *maskPaintDevice = nullptr;
979 QPainter *maskPainter = nullptr;
980 if ( forceVector && !maskLayerHasEffects[ job.layerId ] )
981 {
982 // set a painter to get all masking instruction in order to later clip masked symbol layer
983 auto geomPaintDevice = std::make_unique< QgsGeometryPaintDevice >( );
984 geomPaintDevice->setStrokedPathSegments( 4 );
985 geomPaintDevice->setSimplificationTolerance( job.context()->maskSettings().simplifyTolerance() );
986 maskPaintDevice = geomPaintDevice.release();
987 maskPainter = new QPainter( maskPaintDevice );
988 }
989 else
990 {
991 // Note: we only need an alpha channel here, rather than a full RGBA image
992 QImage *maskImage = nullptr;
993 maskPainter = allocateImageAndPainter( job.layerId, maskImage, job.context() );
994 maskImage->fill( 0 );
995 maskPaintDevice = maskImage;
996 }
997
998 job.context()->setMaskPainter( maskPainter );
999 job.maskPainter.reset( maskPainter );
1000 job.maskPaintDevice.reset( maskPaintDevice );
1001 }
1002 }
1003
1004 for ( LayerRenderJob &job : firstPassJobs )
1005 {
1006 QgsMapLayer *ml = job.layer;
1007
1008 auto it = maskedSymbolLayers.find( job.layerId );
1009 if ( it == maskedSymbolLayers.end() )
1010 continue;
1011
1012 QList<MaskSource> &sourceList = it->second;
1013 const QSet<QString> symbolList = it->first;
1014
1015 secondPassJobs.emplace_back( LayerRenderJob() );
1016 LayerRenderJob &job2 = secondPassJobs.back();
1017
1018 job2.maskRequiresLayerRasterization = job.maskRequiresLayerRasterization;
1019
1020 // Points to the masking jobs. This will be needed during the second pass composition.
1021 for ( MaskSource &source : sourceList )
1022 {
1023 if ( source.labelMaskId != -1 )
1024 job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
1025 else
1026 job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
1027 }
1028
1029 // copy the context from the initial job
1030 job2.setContext( std::make_unique< QgsRenderContext >( *job.context() ) );
1031 // also assign layer to match initial job
1032 job2.layer = job.layer;
1033 job2.renderAboveLabels = job.renderAboveLabels;
1034 job2.layerId = job.layerId;
1035
1036 // associate first pass job with second pass job
1037 job2.firstPassJob = &job;
1038
1039 if ( !forceVector || job2.maskRequiresLayerRasterization )
1040 {
1041 job2.context()->setPainter( allocateImageAndPainter( job.layerId, job2.img, job2.context() ) );
1042 }
1043 else
1044 {
1045 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job2.context() );
1046 if ( job.context()->painter()->hasClipping() )
1047 {
1048 // need to copy clipping paths from original painter, so that e.g. the layout map bounds clipping path is respected
1049 pictureAndPainter.second->setClipping( true );
1050 pictureAndPainter.second->setClipPath( job.context()->painter()->clipPath() );
1051 }
1052 job2.picture = std::move( pictureAndPainter.first );
1053 job2.context()->setPainter( pictureAndPainter.second );
1054 }
1055
1056 if ( ! job2.img && ! job2.picture )
1057 {
1058 secondPassJobs.pop_back();
1059 continue;
1060 }
1061
1062 // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
1063 // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
1064 QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( ml->createMapRenderer( *job2.context() ) );
1065 job2.renderer = mapRenderer;
1066 if ( job2.renderer )
1067 {
1068 job2.context()->setFeedback( job2.renderer->feedback() );
1069 }
1070
1071 // Render only the non masked symbol layer and we will compose 2nd pass with mask and first pass rendering in composeSecondPass
1072 // If vector output is enabled, disabled symbol layers would be actually rendered and masked with clipping path set in QgsMapRendererJob::initSecondPassJobs
1073 job2.context()->setDisabledSymbolLayersV2( symbolList );
1074 }
1075
1076 return secondPassJobs;
1077}
1078
1079QList<QPointer<QgsMapLayer> > QgsMapRendererJob::participatingLabelLayers( QgsLabelingEngine *engine )
1080{
1081 QList<QPointer<QgsMapLayer> > res = _qgis_listRawToQPointer( engine->participatingLayers() );
1082
1083 for ( auto it : std::as_const( mAdditionalLabelLayers ) )
1084 {
1085 if ( !res.contains( it ) )
1086 res.append( it );
1087 }
1088
1089 return res;
1090}
1091
1092void QgsMapRendererJob::initSecondPassJobs( std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob ) const
1093{
1095 return;
1096
1097 for ( LayerRenderJob &job : secondPassJobs )
1098 {
1099 if ( job.maskRequiresLayerRasterization )
1100 continue;
1101
1102 // we draw disabled symbol layer but me mask them with clipping path produced during first pass job
1103 // Resulting 2nd pass job picture will be the final rendering
1104
1105 for ( const QPair<LayerRenderJob *, int> &p : std::as_const( job.maskJobs ) )
1106 {
1107 QPainter *maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[p.second].get();
1108
1109 const QSet<QString> layers = job.context()->disabledSymbolLayersV2();
1110 if ( QgsGeometryPaintDevice *geometryDevice = dynamic_cast<QgsGeometryPaintDevice *>( maskPainter->device() ) )
1111 {
1112 QgsGeometry geometry( geometryDevice->geometry().clone() );
1113
1114#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<10
1115 // structure would be better, but too old GEOS
1116 geometry = geometry.makeValid( Qgis::MakeValidMethod::Linework );
1117#else
1118 geometry = geometry.makeValid( Qgis::MakeValidMethod::Structure );
1119#endif
1120
1121 for ( const QString &symbolLayerId : layers )
1122 {
1123 job.context()->addSymbolLayerClipGeometry( symbolLayerId, geometry );
1124 }
1125 }
1126 }
1127
1128 job.context()->setDisabledSymbolLayersV2( QSet<QString>() );
1129 }
1130}
1131
1132LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
1133{
1134 LabelRenderJob job;
1136 job.context.setPainter( painter );
1137 job.context.setLabelingEngine( labelingEngine2 );
1138 job.context.setFeedback( mLabelingEngineFeedback );
1139 if ( labelingEngine2 )
1140 job.context.labelingEngine()->prepare( job.context );
1141
1143 r1.grow( mSettings.extentBuffer() );
1144 job.context.setExtent( r1 );
1145
1146 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
1149 job.context.setCoordinateTransform( ct );
1150
1151 // no cache, no image allocation
1153 return job;
1154
1155 // if we can use the cache, let's do it and avoid rendering!
1156 bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
1157 if ( hasCache )
1158 {
1159 job.cached = true;
1160 job.complete = true;
1161 job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
1162 Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
1163 job.context.setPainter( nullptr );
1164 }
1165 else
1166 {
1167 if ( canUseLabelCache && ( mCache || !painter ) )
1168 {
1169 job.img = allocateImage( QStringLiteral( "labels" ) );
1170 }
1171 }
1172
1173 return job;
1174}
1175
1176
1177void QgsMapRendererJob::cleanupJobs( std::vector<LayerRenderJob> &jobs )
1178{
1179 for ( LayerRenderJob &job : jobs )
1180 {
1181 if ( job.img )
1182 {
1183 delete job.context()->painter();
1184 job.context()->setPainter( nullptr );
1185
1186 if ( mCache && !job.cached && job.completed && job.layer )
1187 {
1188 QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
1189 mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
1190 mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
1191 }
1192
1193 delete job.img;
1194 job.img = nullptr;
1195 }
1196
1197 if ( job.previewRenderImage )
1198 {
1199 delete job.context()->previewRenderPainter();
1200 job.context()->setPreviewRenderPainter( nullptr );
1201 delete job.previewRenderImage;
1202 job.previewRenderImage = nullptr;
1203 }
1204
1205 if ( job.elevationMap )
1206 {
1207 job.context()->setElevationMap( nullptr );
1208 if ( mCache && !job.cached && job.completed && job.layer )
1209 {
1210 QgsDebugMsgLevel( QStringLiteral( "caching elevation map for %1" ).arg( job.layerId ), 2 );
1212 ELEVATION_MAP_CACHE_PREFIX + job.layerId,
1213 job.elevationMap->rawElevationImage(),
1216 QList< QgsMapLayer * >() << job.layer );
1218 ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ),
1219 job.elevationMap->rawElevationImage(),
1222 QList< QgsMapLayer * >() << job.layer );
1223 }
1224
1225 delete job.elevationMap;
1226 job.elevationMap = nullptr;
1227 }
1228
1229 if ( job.picture )
1230 {
1231 delete job.context()->painter();
1232 job.context()->setPainter( nullptr );
1233 job.picture.reset( nullptr );
1234 }
1235
1236 if ( job.renderer )
1237 {
1238 const QStringList errors = job.renderer->errors();
1239 for ( const QString &message : errors )
1240 mErrors.append( Error( job.renderer->layerId(), message ) );
1241
1242 mRenderedItemResults->appendResults( job.renderer->takeRenderedItemDetails(), *job.context() );
1243
1244 delete job.renderer;
1245 job.renderer = nullptr;
1246 }
1247
1248 if ( job.layer )
1249 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1250
1251 job.maskPainter.reset( nullptr );
1252 job.maskPaintDevice.reset( nullptr );
1253 }
1254
1255 jobs.clear();
1256}
1257
1258void QgsMapRendererJob::cleanupSecondPassJobs( std::vector< LayerRenderJob > &jobs )
1259{
1260 for ( LayerRenderJob &job : jobs )
1261 {
1262 if ( job.img )
1263 {
1264 delete job.context()->painter();
1265 job.context()->setPainter( nullptr );
1266
1267 delete job.img;
1268 job.img = nullptr;
1269 }
1270
1271 if ( job.previewRenderImage )
1272 {
1273 delete job.context()->previewRenderPainter();
1274 job.context()->setPreviewRenderPainter( nullptr );
1275 delete job.previewRenderImage;
1276 job.previewRenderImage = nullptr;
1277 }
1278
1279 if ( job.picture )
1280 {
1281 delete job.context()->painter();
1282 job.context()->setPainter( nullptr );
1283 }
1284
1285 if ( job.renderer )
1286 {
1287 delete job.renderer;
1288 job.renderer = nullptr;
1289 }
1290
1291 if ( job.layer )
1292 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1293 }
1294
1295 jobs.clear();
1296}
1297
1298void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
1299{
1300 if ( job.img )
1301 {
1302 if ( mCache && !job.cached && !job.context.renderingStopped() )
1303 {
1304 QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
1305 mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1306 mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1307 }
1308
1309 delete job.img;
1310 job.img = nullptr;
1311 }
1312
1313 job.picture.reset( nullptr );
1314 job.maskPainters.clear();
1315 job.maskPaintDevices.clear();
1316}
1317
1318
1319#define DEBUG_RENDERING 0
1320
1321QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings,
1322 const std::vector<LayerRenderJob> &jobs,
1323 const LabelRenderJob &labelJob,
1324 const QgsMapRendererCache *cache
1325 )
1326{
1327 QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
1328 image.setDevicePixelRatio( settings.devicePixelRatio() );
1329 image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
1330 image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
1331 image.fill( settings.backgroundColor().rgba() );
1332
1333 const QgsElevationShadingRenderer mapShadingRenderer = settings.elevationShadingRenderer();
1334 std::unique_ptr<QgsElevationMap> mainElevationMap;
1335 if ( mapShadingRenderer.isActive() )
1336 mainElevationMap.reset( new QgsElevationMap( settings.deviceOutputSize(), settings.devicePixelRatio() ) );
1337
1338 QPainter painter( &image );
1339
1340#if DEBUG_RENDERING
1341 int i = 0;
1342#endif
1343 for ( const LayerRenderJob &job : jobs )
1344 {
1345 if ( job.renderAboveLabels )
1346 continue; // skip layer for now, it will be rendered after labels
1347
1348 QImage img = layerImageToBeComposed( settings, job, cache );
1349 if ( img.isNull() )
1350 continue; // image is not prepared and not even in cache
1351
1352 painter.setCompositionMode( job.blendMode );
1353 painter.setOpacity( job.opacity );
1354
1355 if ( mainElevationMap )
1356 {
1357 QgsElevationMap layerElevationMap = layerElevationToBeComposed( settings, job, cache );
1358 if ( layerElevationMap.isValid() )
1359 mainElevationMap->combine( layerElevationMap, mapShadingRenderer.combinedElevationMethod() );
1360 }
1361
1362
1363#if DEBUG_RENDERING
1364 img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
1365 i++;
1366#endif
1367
1368 painter.drawImage( 0, 0, img );
1369 }
1370
1371 if ( mapShadingRenderer.isActive() && mainElevationMap )
1372 {
1373 mapShadingRenderer.renderShading( *mainElevationMap.get(), image, QgsRenderContext::fromMapSettings( settings ) );
1374 }
1375
1376 // IMPORTANT - don't draw labelJob img before the label job is complete,
1377 // as the image is uninitialized and full of garbage before the label job
1378 // commences
1379 if ( labelJob.img && labelJob.complete )
1380 {
1381 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1382 painter.setOpacity( 1.0 );
1383 painter.drawImage( 0, 0, *labelJob.img );
1384 }
1385 else if ( labelJob.picture && labelJob.complete )
1386 {
1387 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1388 painter.setOpacity( 1.0 );
1389 QgsPainting::drawPicture( &painter, QPointF( 0, 0 ), *labelJob.picture );
1390 }
1391 // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
1392 // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
1393 // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
1394 else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
1395 {
1396 const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
1397 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1398 painter.setOpacity( 1.0 );
1399 painter.drawImage( 0, 0, labelCacheImage );
1400 }
1401
1402 // render any layers with the renderAboveLabels flag now
1403 for ( const LayerRenderJob &job : jobs )
1404 {
1405 if ( !job.renderAboveLabels )
1406 continue;
1407
1408 QImage img = layerImageToBeComposed( settings, job, cache );
1409 if ( img.isNull() )
1410 continue; // image is not prepared and not even in cache
1411
1412 painter.setCompositionMode( job.blendMode );
1413 painter.setOpacity( job.opacity );
1414
1415 painter.drawImage( 0, 0, img );
1416 }
1417
1418 painter.end();
1419#if DEBUG_RENDERING
1420 image.save( "/tmp/final.png" );
1421#endif
1422 return image;
1423}
1424
1426 const QgsMapSettings &settings,
1427 const LayerRenderJob &job,
1428 const QgsMapRendererCache *cache
1429)
1430{
1431 if ( job.imageCanBeComposed() )
1432 {
1433 if ( job.previewRenderImage && !job.completed )
1434 return *job.previewRenderImage;
1435
1436 Q_ASSERT( job.img );
1437 return *job.img;
1438 }
1439 else
1440 {
1441 if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
1442 {
1443 return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
1444 }
1445 else
1446 return QImage();
1447 }
1448}
1449
1450QgsElevationMap QgsMapRendererJob::layerElevationToBeComposed( const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache )
1451{
1452 if ( job.imageCanBeComposed() && job.elevationMap )
1453 {
1454 return *job.elevationMap;
1455 }
1456 else
1457 {
1458 if ( cache && cache->hasAnyCacheImage( ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ) ) )
1459 return QgsElevationMap( cache->transformedCacheImage( ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() ) );
1460 else
1461 return QgsElevationMap();
1462 }
1463}
1464
1465void QgsMapRendererJob::composeSecondPass( std::vector<LayerRenderJob> &secondPassJobs, LabelRenderJob &labelJob, bool forceVector )
1466{
1467 // compose the second pass with the mask
1468 for ( LayerRenderJob &job : secondPassJobs )
1469 {
1470 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization;
1471
1472 // Merge all mask images into the first one if we have more than one mask image
1473 if ( isRasterRendering && job.maskJobs.size() > 1 )
1474 {
1475 QPainter *maskPainter = nullptr;
1476 for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
1477 {
1478 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1479 if ( !maskPainter )
1480 {
1481 maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[ p.second ].get();
1482 }
1483 else
1484 {
1485 maskPainter->drawImage( 0, 0, *maskImage );
1486 }
1487 }
1488 }
1489
1490 if ( ! job.maskJobs.isEmpty() )
1491 {
1492 // All have been merged into the first
1493 QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
1494 if ( isRasterRendering )
1495 {
1496 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1497
1498 // Only retain parts of the second rendering that are "inside" the mask image
1499 QPainter *painter = job.context()->painter();
1500
1501 painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1502
1503 //Create an "alpha binarized" image of the maskImage to :
1504 //* Eliminate antialiasing artifact
1505 //* Avoid applying mask opacity to elements under the mask but not masked
1506 QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
1507 QVector<QRgb> mswTable;
1508 mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
1509 mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
1510 maskBinAlpha.setColorTable( mswTable );
1511 painter->drawImage( 0, 0, maskBinAlpha );
1512
1513 // Modify the first pass' image ...
1514 {
1515 QPainter tempPainter;
1516
1517 // reuse the first pass painter, if available
1518 QPainter *painter1 = job.firstPassJob->context()->painter();
1519 if ( ! painter1 )
1520 {
1521 tempPainter.begin( job.firstPassJob->img );
1522 painter1 = &tempPainter;
1523 }
1524
1525 // ... first retain parts that are "outside" the mask image
1526 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
1527 painter1->drawImage( 0, 0, *maskImage );
1528
1529 // ... and overpaint the second pass' image on it
1530 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
1531 painter1->drawImage( 0, 0, *job.img );
1532 }
1533 }
1534 else
1535 {
1536 job.firstPassJob->picture = std::move( job.picture );
1537 job.picture = nullptr;
1538 }
1539 }
1540 }
1541}
1542
1543void QgsMapRendererJob::logRenderingTime( const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob )
1544{
1546 return;
1547
1548 QMultiMap<int, QString> elapsed;
1549 for ( const LayerRenderJob &job : jobs )
1550 elapsed.insert( job.renderingTime, job.layerId );
1551 for ( const LayerRenderJob &job : secondPassJobs )
1552 elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
1553
1554 elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
1555
1556 QList<int> tt( elapsed.uniqueKeys() );
1557 std::sort( tt.begin(), tt.end(), std::greater<int>() );
1558 for ( int t : std::as_const( tt ) )
1559 {
1560 QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
1561 }
1562 QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
1563}
1564
1565void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1566{
1567 QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1568
1569 std::unique_ptr< QgsScopedRuntimeProfile > labelingProfile;
1570 if ( renderContext.flags() & Qgis::RenderContextFlag::RecordProfile )
1571 {
1572 labelingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "(labeling)" ), QStringLiteral( "rendering" ) );
1573 }
1574
1575 QElapsedTimer t;
1576 t.start();
1577
1578 // Reset the composition mode before rendering the labels
1579 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1580
1581 renderContext.setPainter( painter );
1582
1583 if ( labelingEngine2 )
1584 {
1585 labelingEngine2->run( renderContext );
1586 }
1587
1588 QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1589}
1590
1591void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1592{
1593 Q_UNUSED( settings )
1594
1595 drawLabeling( renderContext, labelingEngine2, painter );
1596}
1597
@ 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:78
QString name
Definition qgsmaplayer.h:82
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:85
QString id
Definition qgsmaplayer.h:81
Qgis::LayerType type
Definition qgsmaplayer.h:88
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:90
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:6392
#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