QGIS API Documentation 3.29.0-Master (694ebda27c)
qgsmaprendererjob.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaprendererjob.cpp
3 --------------------------------------
4 Date : December 2013
5 Copyright : (C) 2013 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsmaprendererjob.h"
17
18#include <QPainter>
19#include <QElapsedTimer>
20#include <QTimer>
21#include <QtConcurrentMap>
22
23#include <QPicture>
24
25#include "qgslogger.h"
26#include "qgsrendercontext.h"
27#include "qgsmaplayer.h"
28#include "qgsmaplayerrenderer.h"
29#include "qgsmaprenderercache.h"
30#include "qgsrasterlayer.h"
31#include "qgsmessagelog.h"
32#include "qgspallabeling.h"
33#include "qgsexception.h"
34#include "qgslabelingengine.h"
38#include "qgsrenderer.h"
39#include "qgssymbollayer.h"
40#include "qgsvectorlayerutils.h"
41#include "qgssymbollayerutils.h"
44#include "qgsmaplayerstyle.h"
47#include "qgsmaskpaintdevice.h"
48#include "qgsrasterrenderer.h"
49
51
52const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
53const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
54
55LayerRenderJob &LayerRenderJob::operator=( LayerRenderJob &&other )
56{
57 mContext = std::move( other.mContext );
58
59 img = other.img;
60 other.img = nullptr;
61
62 renderer = other.renderer;
63 other.renderer = nullptr;
64
65 imageInitialized = other.imageInitialized;
66 blendMode = other.blendMode;
67 opacity = other.opacity;
68 cached = other.cached;
69 layer = other.layer;
70 renderAboveLabels = other.renderAboveLabels;
71 completed = other.completed;
72 renderingTime = other.renderingTime;
73 estimatedRenderingTime = other.estimatedRenderingTime ;
74 errors = other.errors;
75 layerId = other.layerId;
76
77 maskPaintDevice = std::move( other.maskPaintDevice );
78
79 firstPassJob = other.firstPassJob;
80 other.firstPassJob = nullptr;
81
82 picture = std::move( other.picture );
83
84 maskJobs = other.maskJobs;
85
86 maskRequiresLayerRasterization = other.maskRequiresLayerRasterization;
87
88 return *this;
89}
90
91LayerRenderJob::LayerRenderJob( LayerRenderJob &&other )
92 : imageInitialized( other.imageInitialized )
93 , blendMode( other.blendMode )
94 , opacity( other.opacity )
95 , cached( other.cached )
96 , renderAboveLabels( other.renderAboveLabels )
97 , layer( other.layer )
98 , completed( other.completed )
99 , renderingTime( other.renderingTime )
100 , estimatedRenderingTime( other.estimatedRenderingTime )
101 , errors( other.errors )
102 , layerId( other.layerId )
103 , maskRequiresLayerRasterization( other.maskRequiresLayerRasterization )
104 , maskJobs( other.maskJobs )
105{
106 mContext = std::move( other.mContext );
107
108 img = other.img;
109 other.img = nullptr;
110
111 renderer = other.renderer;
112 other.renderer = nullptr;
113
114 maskPaintDevice = std::move( other.maskPaintDevice );
115
116 firstPassJob = other.firstPassJob;
117 other.firstPassJob = nullptr;
118
119 picture = std::move( other.picture );
120}
121
122bool LayerRenderJob::imageCanBeComposed() const
123{
124 if ( imageInitialized )
125 {
126 if ( renderer )
127 {
128 return renderer->isReadyToCompose();
129 }
130 else
131 {
132 return true;
133 }
134 }
135 else
136 {
137 return false;
138 }
139}
140
142 : mSettings( settings )
143 , mRenderedItemResults( std::make_unique< QgsRenderedItemResults >( settings.extent() ) )
144 , mLabelingEngineFeedback( new QgsLabelingEngineFeedback( this ) )
145{}
146
148
150{
152 startPrivate();
153 else
154 {
155 mErrors.append( QgsMapRendererJob::Error( QString(), tr( "Invalid map settings" ) ) );
156 emit finished();
157 }
158}
159
161{
163}
164
166{
167 return mRenderedItemResults.release();
168}
169
171 : QgsMapRendererJob( settings )
172{
173}
174
175
177{
178 return mErrors;
179}
180
182{
183 mCache = cache;
184}
185
187{
188 return mLabelingEngineFeedback;
189}
190
191QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
192{
193 QHash<QgsMapLayer *, int> result;
194 for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
195 {
196 if ( auto &&lKey = it.key() )
197 result.insert( lKey, it.value() );
198 }
199 return result;
200}
201
202void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
203{
205}
206
208{
209 return mSettings;
210}
211
213{
214 bool canCache = mCache;
215
216 // calculate which layers will be labeled
217 QSet< QgsMapLayer * > labeledLayers;
218 const QList<QgsMapLayer *> layers = mSettings.layers();
219 for ( QgsMapLayer *ml : layers )
220 {
222 labeledLayers << ml;
223
224 switch ( ml->type() )
225 {
227 {
228 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
229 if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
230 {
231 canCache = false;
232 }
233 break;
234 }
235
237 {
238 // TODO -- add detection of advanced labeling effects for vector tile layers
239 break;
240 }
241
248 break;
249 }
250
251 if ( !canCache )
252 break;
253
254 }
255
257 {
258 // we may need to clear label cache and re-register labeled features - check for that here
259
260 // can we reuse the cached label solution?
261 const QList< QgsMapLayer * > labelDependentLayers = mCache->dependentLayers( LABEL_CACHE_ID );
262 bool canUseCache = canCache && QSet< QgsMapLayer * >( labelDependentLayers.begin(), labelDependentLayers.end() ) == labeledLayers;
263 if ( !canUseCache )
264 {
265 // no - participating layers have changed
267 }
268 }
269 return canCache;
270}
271
272
273bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
274{
275 bool res = true;
276 // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
277 // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
278 QgsCoordinateTransform approxTransform = ct;
279 approxTransform.setBallparkTransformsAreAppropriate( true );
280
281 try
282 {
283#ifdef QGISDEBUG
284 // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
285#endif
286 // Split the extent into two if the source CRS is
287 // geographic and the extent crosses the split in
288 // geographic coordinates (usually +/- 180 degrees,
289 // and is assumed to be so here), and draw each
290 // extent separately.
291 static const double SPLIT_COORD = 180.0;
292
293 if ( ml->crs().isGeographic() )
294 {
295 if ( ml->type() == QgsMapLayerType::VectorLayer && !approxTransform.destinationCrs().isGeographic() )
296 {
297 // if we transform from a projected coordinate system check
298 // check if transforming back roughly returns the input
299 // extend - otherwise render the world.
300 QgsRectangle extent1 = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
301 QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, Qgis::TransformDirection::Forward );
302
303 QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
304 .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
305 .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
306 .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
307 .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
308 , 3 );
309
310 // can differ by a maximum of up to 20% of height/width
311 if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
312 && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
313 && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
314 && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
315 )
316 {
317 extent = extent1;
318 }
319 else
320 {
321 extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
322 res = false;
323 }
324 }
325 else
326 {
327 // Note: ll = lower left point
328 QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
329 Qgis::TransformDirection::Reverse );
330
331 // and ur = upper right point
332 QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
333 Qgis::TransformDirection::Reverse );
334
335 QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
336
337 extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
338
339 QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
340
341 if ( ll.x() > ur.x() )
342 {
343 // the coordinates projected in reverse order than what one would expect.
344 // we are probably looking at an area that includes longitude of 180 degrees.
345 // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
346 // so let's use (-180,180). This hopefully does not add too much overhead. It is
347 // more straightforward than rendering with two separate extents and more consistent
348 // for rendering, labeling and caching as everything is rendered just in one go
349 extent.setXMinimum( -SPLIT_COORD );
350 extent.setXMaximum( SPLIT_COORD );
351 res = false;
352 }
353 }
354
355 // TODO: the above rule still does not help if using a projection that covers the whole
356 // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
357 // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
358 // but in fact the extent should cover the whole world.
359 }
360 else // can't cross 180
361 {
362 if ( approxTransform.destinationCrs().isGeographic() &&
363 ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
364 extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
365 // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
366 // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
367 // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
368 // but this seems like a safer choice.
369 {
370 extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
371 res = false;
372 }
373 else
374 extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
375 }
376 }
377 catch ( QgsCsException & )
378 {
379 QgsDebugMsg( QStringLiteral( "Transform error caught" ) );
380 extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
381 r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
382 res = false;
383 }
384
385 return res;
386}
387
388QImage *QgsMapRendererJob::allocateImage( QString layerId )
389{
390 QImage *image = new QImage( mSettings.deviceOutputSize(),
392 image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
393 image->setDotsPerMeterX( 1000 * mSettings.outputDpi() / 25.4 );
394 image->setDotsPerMeterY( 1000 * mSettings.outputDpi() / 25.4 );
395 if ( image->isNull() )
396 {
397 mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
398 delete image;
399 return nullptr;
400 }
401 return image;
402}
403
404QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image, const QgsRenderContext *context )
405{
406 QPainter *painter = nullptr;
407 image = allocateImage( layerId );
408 if ( image )
409 {
410 painter = new QPainter( image );
411 context->setPainterFlagsUsingContext( painter );
412 }
413 return painter;
414}
415
416QgsMapRendererJob::PictureAndPainter QgsMapRendererJob::allocatePictureAndPainter( const QgsRenderContext *context )
417{
418 std::unique_ptr<QPicture> picture = std::make_unique<QPicture>();
419 QPainter *painter = new QPainter( picture.get() );
420 context->setPainterFlagsUsingContext( painter );
421 return { std::move( picture ), painter };
422}
423
424std::vector<LayerRenderJob> QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
425{
426 std::vector< LayerRenderJob > layerJobs;
427
428 // render all layers in the stack, starting at the base
429 QListIterator<QgsMapLayer *> li( mSettings.layers() );
430 li.toBack();
431
432 if ( mCache )
433 {
435 Q_UNUSED( cacheValid )
436 QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
437 }
438
439 bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
440
441 while ( li.hasPrevious() )
442 {
443 QgsMapLayer *ml = li.previous();
444
445 QgsDebugMsgLevel( QStringLiteral( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
446 .arg( ml->name() )
447 .arg( ml->minimumScale() )
448 .arg( ml->maximumScale() )
449 .arg( ml->hasScaleBasedVisibility() )
450 .arg( ml->blendMode() )
451 .arg( ml->isValid() )
452 , 3 );
453
454 if ( !ml->isValid() )
455 {
456 QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
457 continue;
458 }
459
460 if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
461 {
462 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
463 continue;
464 }
465
467 {
468 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
469 continue;
470 }
471
473 {
474 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
475 continue;
476 }
477
481
482 ct = mSettings.layerTransform( ml );
483 bool haveExtentInLayerCrs = true;
484 if ( ct.isValid() )
485 {
486 haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
487 }
488 QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
489 if ( !r1.isFinite() || !r2.isFinite() )
490 {
491 mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
492 continue;
493 }
494
495 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
496
497 // Force render of layers that are being edited
498 // or if there's a labeling engine that needs the layer to register features
499 if ( mCache )
500 {
501 const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
502 if ( ( vl && vl->isEditable() ) || requiresLabeling )
503 {
504 mCache->clearCacheImage( ml->id() );
505 }
506 }
507
508 layerJobs.emplace_back( LayerRenderJob() );
509 LayerRenderJob &job = layerJobs.back();
510 job.layer = ml;
511 job.layerId = ml->id();
512 job.renderAboveLabels = ml->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool();
513 job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
514
515 job.setContext( std::make_unique< QgsRenderContext >( QgsRenderContext::fromMapSettings( mSettings ) ) );
516 if ( !ml->customProperty( QStringLiteral( "_noset_layer_expression_context" ) ).toBool() )
517 job.context()->expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
518 job.context()->setPainter( painter );
519 job.context()->setLabelingEngine( labelingEngine2 );
520 job.context()->setLabelSink( labelSink() );
521 job.context()->setCoordinateTransform( ct );
522 job.context()->setExtent( r1 );
523 if ( !haveExtentInLayerCrs )
524 job.context()->setFlag( Qgis::RenderContextFlag::ApplyClipAfterReprojection, true );
525
526 if ( mFeatureFilterProvider )
527 job.context()->setFeatureFilterProvider( mFeatureFilterProvider );
528
529 QgsMapLayerStyleOverride styleOverride( ml );
530 if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
531 styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
532
533 job.blendMode = ml->blendMode();
534
535 if ( ml->type() == QgsMapLayerType::RasterLayer )
536 {
537 // raster layers are abnormal wrt opacity handling -- opacity is sometimes handled directly within the raster layer renderer
538 QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( ml );
540 {
541 job.opacity = 1.0;
542 }
543 else
544 {
545 job.opacity = ml->opacity();
546 }
547 }
548 else
549 {
550 job.opacity = ml->opacity();
551 }
552
553 // if we can use the cache, let's do it and avoid rendering!
555 && mCache && mCache->hasCacheImage( ml->id() ) )
556 {
557 job.cached = true;
558 job.imageInitialized = true;
559 job.img = new QImage( mCache->cacheImage( ml->id() ) );
560 job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
561 job.renderer = nullptr;
562 job.context()->setPainter( nullptr );
563 mLayersRedrawnFromCache.append( ml->id() );
564 continue;
565 }
566
567 QElapsedTimer layerTime;
568 layerTime.start();
569 job.renderer = ml->createMapRenderer( *( job.context() ) );
570 if ( job.renderer )
571 {
572 job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
573 job.context()->setFeedback( job.renderer->feedback() );
574 }
575
576 // If we are drawing with an alternative blending mode then we need to render to a separate image
577 // before compositing this on the map. This effectively flattens the layer and prevents
578 // blending occurring between objects on the layer
579 if ( mCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
580 {
581 // Flattened image for drawing when a blending mode is set
582 job.context()->setPainter( allocateImageAndPainter( ml->id(), job.img, job.context() ) );
583 if ( ! job.img )
584 {
585 delete job.renderer;
586 job.renderer = nullptr;
587 layerJobs.pop_back();
588 continue;
589 }
590 }
591
592 job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
593 }
594
595 return layerJobs;
596}
597
598std::vector< LayerRenderJob > QgsMapRendererJob::prepareSecondPassJobs( std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob )
599{
600 std::vector< LayerRenderJob > secondPassJobs;
601
602 // We will need to quickly access the associated rendering job of a layer
603 QHash<QString, LayerRenderJob *> layerJobMapping;
604
605 // ... and layer that contains a mask (and whether there is effects implied or not)
606 QMap<QString, bool> maskLayerHasEffects;
607 QMap<int, bool> labelHasEffects;
608
609 struct MaskSource
610 {
611 QString layerId;
612 QString labelRuleId;
613 int labelMaskId;
614 bool hasEffects;
615 MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_, bool hasEffects_ ):
616 layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ), hasEffects( hasEffects_ ) {}
617 };
618
619 // We collect for each layer, the set of symbol layers that will be "masked"
620 // and the list of source layers that have a mask
621 QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
622
625
626 // First up, create a mapping of layer id to jobs. We need this to filter out any masking
627 // which refers to layers which we aren't rendering as part of this map render
628 for ( LayerRenderJob &job : firstPassJobs )
629 {
630 layerJobMapping[job.layerId] = &job;
631 }
632
633 // next, collate a master list of masked layers, skipping over any which refer to layers
634 // which don't have a corresponding render job
635 for ( LayerRenderJob &job : firstPassJobs )
636 {
637 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
638 if ( ! vl )
639 continue;
640
641 // lambda function to factor code for both label masks and symbol layer masks
642 auto collectMasks = [&]( QgsMaskedLayers * masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
643 {
644 bool hasEffects = false;
645 for ( auto it = masks->begin(); it != masks->end(); ++it )
646 {
647 auto lit = maskedSymbolLayers.find( it.key() );
648 if ( lit == maskedSymbolLayers.end() )
649 {
650 maskedSymbolLayers[it.key()] = qMakePair( it.value().symbolLayerIds, QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId, it.value().hasEffects ) );
651 }
652 else
653 {
654 if ( lit->first != it.value().symbolLayerIds )
655 {
656 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() ) );
657 continue;
658 }
659 lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId, hasEffects ) );
660 }
661 hasEffects |= it.value().hasEffects;
662 }
663 if ( ! masks->isEmpty() && labelMaskId == -1 )
664 maskLayerHasEffects[ sourceLayerId ] = hasEffects;
665 };
666
667 // collect label masks
668 QHash<QString, QgsMaskedLayers> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
669 for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
670 {
671 QString labelRule = it.key();
672 // this is a hash of layer id to masks
673 QgsMaskedLayers masks = it.value();
674
675 // filter out masks to those which we are actually rendering
676 QgsMaskedLayers usableMasks;
677 for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
678 {
679 const QString sourceLayerId = mit.key();
680 // if we aren't rendering the source layer as part of this render, we can't process this mask
681 if ( !layerJobMapping.contains( sourceLayerId ) )
682 continue;
683 else
684 usableMasks.insert( sourceLayerId, mit.value() );
685 }
686
687 if ( usableMasks.empty() )
688 continue;
689
690 // group layers by QSet<QgsSymbolLayerReference>
691 QSet<QgsSymbolLayerReference> slRefs;
692 bool hasEffects = false;
693 for ( auto mit = usableMasks.begin(); mit != usableMasks.end(); mit++ )
694 {
695 const QString sourceLayerId = mit.key();
696 // if we aren't rendering the source layer as part of this render, we can't process this mask
697 if ( !layerJobMapping.contains( sourceLayerId ) )
698 continue;
699
700 for ( auto slIt = mit.value().symbolLayerIds.begin(); slIt != mit.value().symbolLayerIds.end(); slIt++ )
701 {
702 slRefs.insert( QgsSymbolLayerReference( mit.key(), *slIt ) );
703 }
704
705 hasEffects |= mit.value().hasEffects;
706 }
707 // generate a new mask id for this set
708 int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
709 labelHasEffects[ labelMaskId ] = hasEffects;
710
711 // now collect masks
712 collectMasks( &usableMasks, vl->id(), labelRule, labelMaskId );
713 }
714
715 // collect symbol layer masks
717 collectMasks( &symbolLayerMasks, vl->id() );
718 }
719
720 if ( maskedSymbolLayers.isEmpty() )
721 return secondPassJobs;
722
723 // Prepare label mask images
724 for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
725 {
726 QPaintDevice *maskPaintDevice = nullptr;
727 QPainter *maskPainter = nullptr;
728 if ( forceVector && !labelHasEffects[ maskId ] )
729 {
730 // set a painter to get all masking instruction in order to later clip masked symbol layer
731 maskPaintDevice = new QgsMaskPaintDevice( true );
732 maskPainter = new QPainter( maskPaintDevice );
733 }
734 else
735 {
736 // Note: we only need an alpha channel here, rather than a full RGBA image
737 QImage *maskImage = nullptr;
738 maskPainter = allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage, &labelJob.context );
739 maskImage->fill( 0 );
740 maskPaintDevice = maskImage;
741 }
742
743 labelJob.context.setMaskPainter( maskPainter, maskId );
744 labelJob.maskPainters.push_back( std::unique_ptr<QPainter>( maskPainter ) );
745 labelJob.maskPaintDevices.push_back( std::unique_ptr<QPaintDevice>( maskPaintDevice ) );
746 }
747 labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
748
749 // Prepare second pass jobs
750 // - For raster rendering or vector rendering if effects are involved
751 // 1st pass, 2nd pass and mask are rendered in QImage and composed in composeSecondPass
752 // - For vector rendering if no effects are involved
753 // 1st pass is rendered in QImage, clip paths are generated according to mask and used during
754 // masked symbol layer rendering during second pass, which is rendered in QPicture, second
755 // pass job picture
756
757 // Allocate an image for labels
758 if ( !labelJob.img && !forceVector )
759 {
760 labelJob.img = allocateImage( QStringLiteral( "labels" ) );
761 }
762 else if ( !labelJob.picture && forceVector )
763 {
764 labelJob.picture.reset( new QPicture() );
765 }
766
767 // first we initialize painter and mask painter for all jobs
768 for ( LayerRenderJob &job : firstPassJobs )
769 {
770 job.maskRequiresLayerRasterization = false;
771
772 auto it = maskedSymbolLayers.find( job.layerId );
773 if ( it != maskedSymbolLayers.end() )
774 {
775 const QList<MaskSource> &sourceList = it->second;
776 for ( const MaskSource &source : sourceList )
777 {
778 job.maskRequiresLayerRasterization |= source.hasEffects;
779 }
780 }
781
782 // update first pass job painter and device if needed
783 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization || ( job.renderer && job.renderer->forceRasterRender() );
784 if ( isRasterRendering && !job.img )
785 {
786 job.context()->setPainter( allocateImageAndPainter( job.layerId, job.img, job.context() ) );
787 }
788 else if ( !isRasterRendering && !job.picture )
789 {
790 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job.context() );
791 job.picture = std::move( pictureAndPainter.first );
792 job.context()->setPainter( pictureAndPainter.second );
793 // force recreation of layer renderer so it initialize correctly the renderer
794 // especially the RasterLayerRender that need logicalDpiX from painting device
795 job.renderer = job.layer->createMapRenderer( *( job.context() ) );
796 }
797
798 // for layer that mask, generate mask in first pass job
799 if ( maskLayerHasEffects.contains( job.layerId ) )
800 {
801 QPaintDevice *maskPaintDevice = nullptr;
802 QPainter *maskPainter = nullptr;
803 if ( forceVector && !maskLayerHasEffects[ job.layerId ] )
804 {
805 // set a painter to get all masking instruction in order to later clip masked symbol layer
806 maskPaintDevice = new QgsMaskPaintDevice();
807 maskPainter = new QPainter( maskPaintDevice );
808 }
809 else
810 {
811 // Note: we only need an alpha channel here, rather than a full RGBA image
812 QImage *maskImage = nullptr;
813 maskPainter = allocateImageAndPainter( job.layerId, maskImage, job.context() );
814 maskImage->fill( 0 );
815 maskPaintDevice = maskImage;
816 }
817
818 job.context()->setMaskPainter( maskPainter );
819 job.maskPainter.reset( maskPainter );
820 job.maskPaintDevice.reset( maskPaintDevice );
821 }
822 }
823
824 for ( LayerRenderJob &job : firstPassJobs )
825 {
826 QgsMapLayer *ml = job.layer;
827
828 auto it = maskedSymbolLayers.find( job.layerId );
829 if ( it == maskedSymbolLayers.end() )
830 continue;
831
832 QList<MaskSource> &sourceList = it->second;
833 const QSet<QgsSymbolLayerId> &symbolList = it->first;
834
835 secondPassJobs.emplace_back( LayerRenderJob() );
836 LayerRenderJob &job2 = secondPassJobs.back();
837
838 job2.maskRequiresLayerRasterization = job.maskRequiresLayerRasterization;
839
840 // Points to the masking jobs. This will be needed during the second pass composition.
841 for ( MaskSource &source : sourceList )
842 {
843 if ( source.labelMaskId != -1 )
844 job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
845 else
846 job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
847 }
848
849 // copy the context from the initial job
850 job2.setContext( std::make_unique< QgsRenderContext >( *job.context() ) );
851 // also assign layer to match initial job
852 job2.layer = job.layer;
853 job2.renderAboveLabels = job.renderAboveLabels;
854 job2.layerId = job.layerId;
855
856 // associate first pass job with second pass job
857 job2.firstPassJob = &job;
858
859 if ( !forceVector || job2.maskRequiresLayerRasterization )
860 {
861 job2.context()->setPainter( allocateImageAndPainter( job.layerId, job2.img, job2.context() ) );
862 }
863 else
864 {
865 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job2.context() );
866 job2.picture = std::move( pictureAndPainter.first );
867 job2.context()->setPainter( pictureAndPainter.second );
868 }
869
870 if ( ! job2.img && ! job2.picture )
871 {
872 secondPassJobs.pop_back();
873 continue;
874 }
875
876 // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
877 // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
878 QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( ml->createMapRenderer( *job2.context() ) );
879 job2.renderer = mapRenderer;
880 if ( job2.renderer )
881 {
882 job2.context()->setFeedback( job2.renderer->feedback() );
883 }
884
885 // Render only the non masked symbol layer and we will compose 2nd pass with mask and first pass rendering in composeSecondPass
886 // If vector output is enabled, disabled symbol layers would be actually rendered and masked with clipping path set in QgsMapRendererJob::initSecondPassJobs
887 job2.context()->setDisabledSymbolLayers( QgsSymbolLayerUtils::toSymbolLayerPointers( mapRenderer->featureRenderer(), symbolList ) );
888 }
889
890 return secondPassJobs;
891}
892
893void QgsMapRendererJob::initSecondPassJobs( std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob ) const
894{
896 return;
897
898 for ( LayerRenderJob &job : secondPassJobs )
899 {
900 if ( job.maskRequiresLayerRasterization )
901 continue;
902
903 // we draw disabled symbol layer but me mask them with clipping path produced during first pass job
904 // Resulting 2nd pass job picture will be the final rendering
905
906 for ( const QPair<LayerRenderJob *, int> &p : std::as_const( job.maskJobs ) )
907 {
908 QPainter *maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[p.second].get();
909 QPainterPath path = static_cast<QgsMaskPaintDevice *>( maskPainter->device() )->maskPainterPath();
910 for ( const QgsSymbolLayer *symbolLayer : job.context()->disabledSymbolLayers() )
911 {
912 job.context()->addSymbolLayerClipPath( symbolLayer, path );
913 }
914 }
915
916 job.context()->setDisabledSymbolLayers( QSet<const QgsSymbolLayer *>() );
917 }
918}
919
920LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
921{
922 LabelRenderJob job;
924 job.context.setPainter( painter );
925 job.context.setLabelingEngine( labelingEngine2 );
926 job.context.setFeedback( mLabelingEngineFeedback );
927
930 job.context.setExtent( r1 );
931
932 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
935 job.context.setCoordinateTransform( ct );
936
937 // no cache, no image allocation
939 return job;
940
941 // if we can use the cache, let's do it and avoid rendering!
942 bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
943 if ( hasCache )
944 {
945 job.cached = true;
946 job.complete = true;
947 job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
948 Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
949 job.context.setPainter( nullptr );
950 }
951 else
952 {
953 if ( canUseLabelCache && ( mCache || !painter ) )
954 {
955 job.img = allocateImage( QStringLiteral( "labels" ) );
956 }
957 }
958
959 return job;
960}
961
962
963void QgsMapRendererJob::cleanupJobs( std::vector<LayerRenderJob> &jobs )
964{
965 for ( LayerRenderJob &job : jobs )
966 {
967 if ( job.img )
968 {
969 delete job.context()->painter();
970 job.context()->setPainter( nullptr );
971
972 if ( mCache && !job.cached && job.completed && job.layer )
973 {
974 QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
975 mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
976 mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
977 }
978
979 delete job.img;
980 job.img = nullptr;
981 }
982
983 if ( job.picture )
984 {
985 delete job.context()->painter();
986 job.context()->setPainter( nullptr );
987 job.picture.reset( nullptr );
988 }
989
990 if ( job.renderer )
991 {
992 const QStringList errors = job.renderer->errors();
993 for ( const QString &message : errors )
994 mErrors.append( Error( job.renderer->layerId(), message ) );
995
996 mRenderedItemResults->appendResults( job.renderer->takeRenderedItemDetails(), *job.context() );
997
998 delete job.renderer;
999 job.renderer = nullptr;
1000 }
1001
1002 if ( job.layer )
1003 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1004
1005 job.maskPainter.reset( nullptr );
1006 job.maskPaintDevice.reset( nullptr );
1007 }
1008
1009 jobs.clear();
1010}
1011
1012void QgsMapRendererJob::cleanupSecondPassJobs( std::vector< LayerRenderJob > &jobs )
1013{
1014 for ( LayerRenderJob &job : jobs )
1015 {
1016 if ( job.img )
1017 {
1018 delete job.context()->painter();
1019 job.context()->setPainter( nullptr );
1020
1021 delete job.img;
1022 job.img = nullptr;
1023 }
1024
1025 if ( job.picture )
1026 {
1027 delete job.context()->painter();
1028 job.context()->setPainter( nullptr );
1029 }
1030
1031 if ( job.renderer )
1032 {
1033 delete job.renderer;
1034 job.renderer = nullptr;
1035 }
1036
1037 if ( job.layer )
1038 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1039 }
1040
1041 jobs.clear();
1042}
1043
1044void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
1045{
1046 if ( job.img )
1047 {
1048 if ( mCache && !job.cached && !job.context.renderingStopped() )
1049 {
1050 QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
1051 mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1052 mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1053 }
1054
1055 delete job.img;
1056 job.img = nullptr;
1057 }
1058
1059 job.picture.reset( nullptr );
1060 job.maskPainters.clear();
1061 job.maskPaintDevices.clear();
1062}
1063
1064
1065#define DEBUG_RENDERING 0
1066
1067QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings,
1068 const std::vector<LayerRenderJob> &jobs,
1069 const LabelRenderJob &labelJob,
1070 const QgsMapRendererCache *cache
1071 )
1072{
1073 QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
1074 image.setDevicePixelRatio( settings.devicePixelRatio() );
1075 image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
1076 image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
1077 image.fill( settings.backgroundColor().rgba() );
1078
1079 QPainter painter( &image );
1080
1081#if DEBUG_RENDERING
1082 int i = 0;
1083#endif
1084 for ( const LayerRenderJob &job : jobs )
1085 {
1086 if ( job.renderAboveLabels )
1087 continue; // skip layer for now, it will be rendered after labels
1088
1089 QImage img = layerImageToBeComposed( settings, job, cache );
1090 if ( img.isNull() )
1091 continue; // image is not prepared and not even in cache
1092
1093 painter.setCompositionMode( job.blendMode );
1094 painter.setOpacity( job.opacity );
1095
1096#if DEBUG_RENDERING
1097 img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
1098 i++;
1099#endif
1100
1101 painter.drawImage( 0, 0, img );
1102 }
1103
1104 // IMPORTANT - don't draw labelJob img before the label job is complete,
1105 // as the image is uninitialized and full of garbage before the label job
1106 // commences
1107 if ( labelJob.img && labelJob.complete )
1108 {
1109 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1110 painter.setOpacity( 1.0 );
1111 painter.drawImage( 0, 0, *labelJob.img );
1112 }
1113 // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
1114 // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
1115 // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
1116 else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
1117 {
1118 const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
1119 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1120 painter.setOpacity( 1.0 );
1121 painter.drawImage( 0, 0, labelCacheImage );
1122 }
1123
1124 // render any layers with the renderAboveLabels flag now
1125 for ( const LayerRenderJob &job : jobs )
1126 {
1127 if ( !job.renderAboveLabels )
1128 continue;
1129
1130 QImage img = layerImageToBeComposed( settings, job, cache );
1131 if ( img.isNull() )
1132 continue; // image is not prepared and not even in cache
1133
1134 painter.setCompositionMode( job.blendMode );
1135 painter.setOpacity( job.opacity );
1136
1137 painter.drawImage( 0, 0, img );
1138 }
1139
1140 painter.end();
1141#if DEBUG_RENDERING
1142 image.save( "/tmp/final.png" );
1143#endif
1144 return image;
1145}
1146
1148 const QgsMapSettings &settings,
1149 const LayerRenderJob &job,
1150 const QgsMapRendererCache *cache
1151)
1152{
1153 if ( job.imageCanBeComposed() )
1154 {
1155 Q_ASSERT( job.img );
1156 return *job.img;
1157 }
1158 else
1159 {
1160 if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
1161 {
1162 return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
1163 }
1164 else
1165 return QImage();
1166 }
1167}
1168
1169void QgsMapRendererJob::composeSecondPass( std::vector<LayerRenderJob> &secondPassJobs, LabelRenderJob &labelJob, bool forceVector )
1170{
1171 // compose the second pass with the mask
1172 for ( LayerRenderJob &job : secondPassJobs )
1173 {
1174 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization;
1175
1176 // Merge all mask images into the first one if we have more than one mask image
1177 if ( isRasterRendering && job.maskJobs.size() > 1 )
1178 {
1179 QPainter *maskPainter = nullptr;
1180 for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
1181 {
1182 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1183 if ( !maskPainter )
1184 {
1185 maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[ p.second ].get();
1186 }
1187 else
1188 {
1189 maskPainter->drawImage( 0, 0, *maskImage );
1190 }
1191 }
1192 }
1193
1194 if ( ! job.maskJobs.isEmpty() )
1195 {
1196 // All have been merged into the first
1197 QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
1198 if ( isRasterRendering )
1199 {
1200 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1201
1202 // Only retain parts of the second rendering that are "inside" the mask image
1203 QPainter *painter = job.context()->painter();
1204
1205 painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1206
1207 //Create an "alpha binarized" image of the maskImage to :
1208 //* Eliminate antialiasing artifact
1209 //* Avoid applying mask opacity to elements under the mask but not masked
1210 QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
1211 QVector<QRgb> mswTable;
1212 mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
1213 mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
1214 maskBinAlpha.setColorTable( mswTable );
1215 painter->drawImage( 0, 0, maskBinAlpha );
1216
1217 // Modify the first pass' image ...
1218 {
1219 QPainter tempPainter;
1220
1221 // reuse the first pass painter, if available
1222 QPainter *painter1 = job.firstPassJob->context()->painter();
1223 if ( ! painter1 )
1224 {
1225 tempPainter.begin( job.firstPassJob->img );
1226 painter1 = &tempPainter;
1227 }
1228
1229 // ... first retain parts that are "outside" the mask image
1230 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
1231 painter1->drawImage( 0, 0, *maskImage );
1232
1233 // ... and overpaint the second pass' image on it
1234 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
1235 painter1->drawImage( 0, 0, *job.img );
1236 }
1237 }
1238 else
1239 {
1240 job.firstPassJob->picture = std::move( job.picture );
1241 job.picture = nullptr;
1242 }
1243 }
1244 }
1245}
1246
1247void QgsMapRendererJob::logRenderingTime( const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob )
1248{
1250 return;
1251
1252 QMultiMap<int, QString> elapsed;
1253 for ( const LayerRenderJob &job : jobs )
1254 elapsed.insert( job.renderingTime, job.layerId );
1255 for ( const LayerRenderJob &job : secondPassJobs )
1256 elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
1257
1258 elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
1259
1260 QList<int> tt( elapsed.uniqueKeys() );
1261 std::sort( tt.begin(), tt.end(), std::greater<int>() );
1262 for ( int t : std::as_const( tt ) )
1263 {
1264 QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
1265 }
1266 QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
1267}
1268
1269void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1270{
1271 QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1272
1273 QElapsedTimer t;
1274 t.start();
1275
1276 // Reset the composition mode before rendering the labels
1277 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1278
1279 renderContext.setPainter( painter );
1280
1281 if ( labelingEngine2 )
1282 {
1283 labelingEngine2->run( renderContext );
1284 }
1285
1286 QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1287}
1288
1289void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1290{
1291 Q_UNUSED( settings )
1292
1293 drawLabeling( renderContext, labelingEngine2, painter );
1294}
1295
@ InternalLayerOpacityHandling
The renderer internally handles the raster layer's opacity, so the default layer level opacity handli...
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ 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 ...
Class for doing transforms between two map coordinate systems.
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.
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...
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition: qgsrange.h:247
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
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.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
virtual bool isVisibleInZRange(const QgsDoubleRange &range) const
Returns true if the layer should be visible and rendered for the specified z range.
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:73
QString name
Definition: qgsmaplayer.h:76
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.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
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.
Definition: qgsmaplayer.h:1523
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
bool isValid
Definition: qgsmaplayer.h:81
double minimumScale() const
Returns the minimum map scale (i.e.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1530
double opacity
Definition: qgsmaplayer.h:82
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)
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 QgsSettingsEntryBool settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
static const QString LABEL_PREVIEW_CACHE_ID
QgsMapRendererCache ID string for cached label image during preview compositions only.
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
QgsMapRendererJob(const QgsMapSettings &settings)
~QgsMapRendererJob() override
void start()
Start the rendering job and immediately return.
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.
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).
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...
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
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.
Mask painter device that can be used to register everything painted into a QPainterPath used later as...
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:59
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
Definition: qgspointxy.cpp:51
Q_GADGET double x
Definition: qgspointxy.h:62
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.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Definition: qgsrectangle.h:559
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
Stores collated details of rendered items during a map rendering operation.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
Type used to refer to a specific symbol layer in a symbol of a layer.
static QSet< const QgsSymbolLayer * > toSymbolLayerPointers(QgsFeatureRenderer *renderer, const QSet< QgsSymbolLayerId > &symbolLayerIds)
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature...
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.
QgsFeatureRenderer * featureRenderer()
Returns the feature renderer.
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.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3063
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QHash< QString, QgsMaskedLayer > QgsMaskedLayers