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