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