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