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