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