19 #include <QElapsedTimer> 21 #include <QtConcurrentMap> 51 : mSettings( settings )
75 QHash<QgsMapLayer *, int> result;
79 result.insert( it.key(), it.value() );
94 QSet< QgsMapLayer * > labeledLayers;
138 static const double SPLIT_COORD = 180.0;
150 QgsDebugMsgLevel( QStringLiteral(
"\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
153 .arg( std::fabs( 1.0 - extent2.
width() / extent.
width() ) )
154 .arg( std::fabs( 1.0 - extent2.
height() / extent.
height() ) )
187 if ( ll.
x() > ur.
x() )
214 extent =
QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
222 QgsDebugMsg( QStringLiteral(
"Transform error caught" ) );
223 extent =
QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
224 r2 =
QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
230 QImage *QgsMapRendererJob::allocateImage( QString layerId )
235 if ( image->isNull() )
244 QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image )
246 QPainter *painter =
nullptr;
247 image = allocateImage( layerId );
250 painter =
new QPainter( image );
258 LayerRenderJobs layerJobs;
267 Q_UNUSED( cacheValid )
268 QgsDebugMsgLevel( QStringLiteral(
"CACHE VALID: %1" ).arg( cacheValid ), 4 );
273 while ( li.hasPrevious() )
277 QgsDebugMsgLevel( QStringLiteral(
"layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
294 QgsDebugMsgLevel( QStringLiteral(
"Layer not rendered because it is not within the defined visibility scale range" ), 3 );
305 reprojectToLayerExtent( ml, ct, r1, r2 );
310 mErrors.append( Error( ml->
id(), tr(
"There was a problem transforming the layer's extent. Layer skipped." ) ) );
320 bool requiresLabeling =
false;
328 layerJobs.append( LayerRenderJob() );
329 LayerRenderJob &job = layerJobs.last();
333 job.layerId = ml->
id();
334 job.renderingTime = -1;
338 job.context.setPainter( painter );
339 job.context.setLabelingEngine( labelingEngine2 );
340 job.context.setCoordinateTransform( ct );
341 job.context.setExtent( r1 );
343 if ( mFeatureFilterProvider )
344 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
361 job.imageInitialized =
true;
364 job.renderer =
nullptr;
365 job.context.setPainter(
nullptr );
372 if (
mCache || ( !painter && !deferredPainterSet ) || needTemporaryImage( ml ) )
375 job.context.setPainter( allocateImageAndPainter( ml->
id(), job.img ) );
378 layerJobs.removeLast();
383 QElapsedTimer layerTime;
386 job.renderingTime = layerTime.elapsed();
394 LayerRenderJobs secondPassJobs;
397 QHash<QString, LayerRenderJob *> layerJobMapping;
400 QSet<QString> layerHasMask;
407 MaskSource(
const QString &layerId_,
const QString &labelRuleId_,
int labelMaskId_ ):
408 layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ) {}
413 QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
415 for ( LayerRenderJob &job : firstPassJobs )
421 layerJobMapping[job.layer->id()] = &job;
424 auto collectMasks = [&]( QHash<QString, QSet<QgsSymbolLayerId>> *masks, QString sourceLayerId, QString ruleId = QString(),
int labelMaskId = -1 )
426 for (
auto it = masks->begin(); it != masks->end(); ++it )
428 auto lit = maskedSymbolLayers.find( it.key() );
429 if ( lit == maskedSymbolLayers.end() )
431 maskedSymbolLayers[it.key()] = qMakePair( it.value(), QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId ) );
435 if ( lit->first != it.value() )
437 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() ) );
440 lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId ) );
443 if ( ! masks->isEmpty() )
444 layerHasMask.insert( sourceLayerId );
449 for (
auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
451 QString labelRule = it.key();
452 QHash<QString, QSet<QgsSymbolLayerId>> masks = it.value();
455 QSet<QgsSymbolLayerReference> slRefs;
456 for (
auto mit = masks.begin(); mit != masks.end(); mit++ )
458 for (
auto slIt = mit.value().begin(); slIt != mit.value().end(); slIt++ )
464 int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->
id(), it.key(), slRefs );
467 collectMasks( &masks, vl->
id(), labelRule, labelMaskId );
472 collectMasks( &symbolLayerMasks, vl->
id() );
475 if ( maskedSymbolLayers.isEmpty() )
476 return secondPassJobs;
480 for ( LayerRenderJob &job : firstPassJobs )
484 if ( job.img ==
nullptr )
486 job.context.setPainter( allocateImageAndPainter( ml->
id(), job.img ) );
488 if ( layerHasMask.contains( ml->
id() ) )
491 job.context.setMaskPainter( allocateImageAndPainter( ml->
id(), job.maskImage ) );
492 job.maskImage->fill( 0 );
497 if ( labelJob.img ==
nullptr )
499 labelJob.img = allocateImage( QStringLiteral(
"labels" ) );
503 for (
int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
506 labelJob.context.setMaskPainter( allocateImageAndPainter( QStringLiteral(
"label mask" ), maskImage ), maskId );
507 maskImage->fill( 0 );
508 labelJob.maskImages.push_back( maskImage );
510 labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
513 for ( LayerRenderJob &job : firstPassJobs )
517 auto it = maskedSymbolLayers.find( ml->
id() );
518 if ( it == maskedSymbolLayers.end() )
521 QList<MaskSource> &sourceList = it->second;
522 const QSet<QgsSymbolLayerId> &symbolList = it->first;
525 secondPassJobs.append( LayerRenderJob() );
526 LayerRenderJob &job2 = secondPassJobs.last();
529 job2.firstPassJob = &job;
533 job2.context.setMaskPainter(
nullptr );
534 job2.context.setPainter( allocateImageAndPainter( vl1->
id(), job2.img ) );
537 secondPassJobs.removeLast();
542 for ( MaskSource &source : sourceList )
544 if ( source.labelMaskId != -1 )
545 job2.maskJobs.push_back( qMakePair(
nullptr, source.labelMaskId ) );
547 job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
553 job2.renderer = mapRenderer;
560 return secondPassJobs;
567 job.context.setPainter( painter );
568 job.context.setLabelingEngine( labelingEngine2 );
570 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
580 job.context.setPainter(
nullptr );
584 if ( canUseLabelCache && (
mCache || !painter ) )
586 job.img = allocateImage( QStringLiteral(
"labels" ) );
596 for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
598 LayerRenderJob &job = *it;
601 delete job.context.painter();
602 job.context.setPainter(
nullptr );
604 if (
mCache && !job.cached && !job.context.renderingStopped() && job.layer )
606 QgsDebugMsgLevel( QStringLiteral(
"caching image for %1" ).arg( job.layerId ), 2 );
617 delete job.context.maskPainter();
618 job.context.setMaskPainter(
nullptr );
619 delete job.maskImage;
624 const auto constErrors = job.renderer->errors();
625 for (
const QString &message : constErrors )
626 mErrors.append( Error( job.renderer->layerId(), message ) );
629 job.renderer =
nullptr;
641 for (
auto &job : jobs )
645 delete job.context.painter();
646 job.context.setPainter(
nullptr );
655 job.renderer =
nullptr;
669 if (
mCache && !job.cached && !job.context.renderingStopped() )
679 for (
int maskId = 0; maskId < job.maskImages.size(); maskId++ )
681 delete job.context.maskPainter( maskId );
682 job.context.setMaskPainter(
nullptr, maskId );
683 delete job.maskImages[maskId];
688 #define DEBUG_RENDERING 0 696 QPainter painter( &image );
701 for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
703 const LayerRenderJob &job = *it;
705 if ( job.layer && job.layer->customProperty( QStringLiteral(
"rendering/renderAboveLabels" ) ).toBool() )
708 if ( !job.imageInitialized )
711 painter.setCompositionMode( job.blendMode );
712 painter.setOpacity( job.opacity );
715 job.img->save( QString(
"/tmp/final_%1.png" ).arg( i ) );
720 painter.drawImage( 0, 0, *job.img );
726 if ( labelJob.img && labelJob.complete )
728 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
729 painter.setOpacity( 1.0 );
730 painter.drawImage( 0, 0, *labelJob.img );
734 for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
736 const LayerRenderJob &job = *it;
738 if ( !job.layer || !job.layer->customProperty( QStringLiteral(
"rendering/renderAboveLabels" ) ).toBool() )
741 if ( !job.imageInitialized )
744 painter.setCompositionMode( job.blendMode );
745 painter.setOpacity( job.opacity );
749 painter.drawImage( 0, 0, *job.img );
754 image.save(
"/tmp/final.png" );
765 for ( LayerRenderJob &job : secondPassJobs )
769 job.img->save( QString(
"/tmp/second_%1.png" ).arg( i ) );
774 if ( job.maskJobs.size() > 1 )
776 QPainter *maskPainter =
nullptr;
777 for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
779 QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
781 maskImage->save( QString(
"/tmp/mask_%1_%2.png" ).arg( i ).arg( mask++ ) );
785 maskPainter = p.first ? p.first->context.maskPainter() : labelJob.context.maskPainter( p.second );
789 maskPainter->drawImage( 0, 0, *maskImage );
794 if ( ! job.maskJobs.isEmpty() )
797 QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
798 QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
800 maskImage->save( QString(
"/tmp/mask_%1.png" ).arg( i ) );
804 QPainter *painter = job.context.painter();
805 painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
806 painter->drawImage( 0, 0, *maskImage );
808 job.img->save( QString(
"/tmp/second_%1_a.png" ).arg( i ) );
813 QPainter tempPainter;
816 QPainter *painter1 = job.firstPassJob->context.painter();
819 tempPainter.begin( job.firstPassJob->img );
820 painter1 = &tempPainter;
823 job.firstPassJob->img->save( QString(
"/tmp/second_%1_first_pass_1.png" ).arg( i ) );
826 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
827 painter1->drawImage( 0, 0, *maskImage );
830 job.firstPassJob->img->save( QString(
"/tmp/second_%1_first_pass_2.png" ).arg( i ) );
833 painter1->setCompositionMode( QPainter::CompositionMode_SourceOver );
834 painter1->drawImage( 0, 0, *job.img );
836 job.img->save( QString(
"/tmp/second_%1_b.png" ).arg( i ) );
837 if ( job.firstPassJob )
838 job.firstPassJob->img->save( QString(
"/tmp/second_%1_first_pass_3.png" ).arg( i ) );
848 if ( !settings.
value( QStringLiteral(
"Map/logCanvasRefreshEvent" ),
false ).toBool() )
851 QMultiMap<int, QString> elapsed;
852 const auto constJobs =
jobs;
853 for (
const LayerRenderJob &job : constJobs )
854 elapsed.insert( job.renderingTime, job.layerId );
855 const auto constSecondPassJobs = secondPassJobs;
856 for (
const LayerRenderJob &job : constSecondPassJobs )
857 elapsed.insert( job.renderingTime, job.layerId + QString(
" (second pass)" ) );
859 elapsed.insert( labelJob.renderingTime, tr(
"Labeling" ) );
861 QList<int> tt( elapsed.uniqueKeys() );
862 std::sort( tt.begin(), tt.end(), std::greater<int>() );
863 const auto constTt = tt;
864 for (
int t : constTt )
866 QgsMessageLog::logMessage( tr(
"%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QStringLiteral(
", " ) ) ), tr(
"Rendering" ) );
871 bool QgsMapRendererJob::needTemporaryImage(
QgsMapLayer *ml )
873 switch ( ml->
type() )
884 ( ( vl->
blendMode() != QPainter::CompositionMode_SourceOver )
917 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
921 if ( labelingEngine2 )
923 labelingEngine2->
run( renderContext );
926 QgsDebugMsgLevel( QStringLiteral(
"Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
933 drawLabeling( renderContext, labelingEngine2, painter );
QList< QgsMapLayer *> dependentLayers(const QString &cacheKey) const
Returns a list of map layers on which an image in the cache depends.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
A rectangle specified with double values.
Base class for all map layer types.
const LayerRenderJobs & jobs() const
Abstract base class for map rendering implementations.
QgsMapLayerType type() const
Returns the type of the layer.
QgsFeatureRenderer * featureRenderer()
Returns the feature renderer.
void setXMaximum(double x)
Set the maximum x value.
This class is a composition of two QSettings instances:
void cleanupJobs(LayerRenderJobs &jobs)
Restore overridden layer style on destruction.
QSize deviceOutputSize() const
Returns the device output size of the map canvas This is equivalent to the output size multiplicated ...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
static void warning(const QString &msg)
Goes to qWarning.
A class to represent a 2D point.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
QColor backgroundColor() const
Gets the background color of the map.
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
bool forceRasterRender() const
Returns whether the renderer must render as a raster.
void setCacheImage(const QString &cacheKey, const QImage &image, const QList< QgsMapLayer * > &dependentLayers=QList< QgsMapLayer * >())
Set the cached image for a particular cacheKey.
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
QList< QgsMapLayer * > layers() const
Gets list of layers for map rendering The layers are stored in the reverse order of how they are rend...
static const QString LABEL_CACHE_ID
QgsMapRendererCache ID string for cached label image.
bool isValid() const
Returns the status of the layer.
Enable layer opacity and blending effects.
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...
static QHash< QString, QSet< QgsSymbolLayerId > > symbolLayerMasks(const QgsVectorLayer *)
Returns all masks that may be defined on symbol layers for a given vector layer.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
The QgsMapSettings class contains configuration for rendering of the map.
static bool staticWillUseLayer(QgsVectorLayer *layer)
called to find out whether the layer is used for labeling
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
QHash< QgsWeakMapLayerPointer, int > mPerLayerRenderingTime
Render time (in ms) per layer, by layer ID.
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
#define QgsDebugMsgLevel(str, level)
void grow(double delta)
Grows the rectangle in place by the specified amount.
bool init(const QgsRectangle &extent, double scale)
Initialize cache: set new parameters and clears the cache if any parameters have changed since last i...
double scale() const
Returns the calculated map scale.
virtual void run(QgsRenderContext &context)=0
Runs the labeling job.
double width() const
Returns the width of the rectangle.
QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext) FINAL
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Enable anti-aliasing for map rendering.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
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...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QImage cacheImage(const QString &cacheKey) const
Returns the cached image for the specified cacheKey.
QgsFeatureRenderer * renderer()
Returns renderer.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
double minimumScale() const
Returns the minimum map scale (i.e.
double maximumScale() const
Returns the maximum map scale (i.e.
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
float devicePixelRatio() const
Returns device pixel ratio Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" di...
QPainter::CompositionMode featureBlendMode() const
Returns the current blending mode for features.
bool isFinite() const
Returns true if the rectangle has finite boundaries.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Implementation of threaded rendering for vector layers.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
bool hasCacheImage(const QString &cacheKey) const
Returns true if the cache contains an image with the specified cacheKey.
The QgsLabelingEngine class provides map labeling functionality.
Contains information about the context of a rendering operation.
static QHash< QString, QHash< QString, QSet< QgsSymbolLayerId > > > labelMasks(const QgsVectorLayer *)
Returns masks defined in labeling options of a layer.
QMap< QString, QString > layerStyleOverrides() const
Gets map of map layer style overrides (key: layer ID, value: style name) where a different style shou...
QList< QgsMapRendererJob::Error > Errors
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void clearCacheImage(const QString &cacheKey)
Removes an image from the cache with matching cacheKey.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context...
QgsMapRendererQImageJob(const QgsMapSettings &settings)
double yMaximum() const
Returns the y maximum value (top side of rectangle).
This class is responsible for keeping cache of rendered images resulting from a map rendering job...
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
Custom exception class for Coordinate Reference System related exceptions.
Type used to refer to a specific symbol layer in a symbol of a layer.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Represents a vector layer which manages a vector based data sets.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
QgsMapRendererCache * mCache
QSize outputSize() const
Returns the size of the resulting map image.
QgsMapRendererJob(const QgsMapSettings &settings)
void setXMinimum(double x)
Set the minimum x value.
QgsCoordinateReferenceSystem crs
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
double height() const
Returns the height of the rectangle.