LCOV - code coverage report
Current view: top level - core - qgsmaprendererjob.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 2 510 0.4 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       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 "qgslogger.h"
      24                 :            : #include "qgsrendercontext.h"
      25                 :            : #include "qgsmaplayer.h"
      26                 :            : #include "qgsproject.h"
      27                 :            : #include "qgsmaplayerrenderer.h"
      28                 :            : #include "qgsmaplayerstylemanager.h"
      29                 :            : #include "qgsmaprenderercache.h"
      30                 :            : #include "qgsmessagelog.h"
      31                 :            : #include "qgspallabeling.h"
      32                 :            : #include "qgsexception.h"
      33                 :            : #include "qgslabelingengine.h"
      34                 :            : #include "qgsmaplayerlistutils.h"
      35                 :            : #include "qgsvectorlayerlabeling.h"
      36                 :            : #include "qgssettings.h"
      37                 :            : #include "qgsexpressioncontextutils.h"
      38                 :            : #include "qgssymbol.h"
      39                 :            : #include "qgsrenderer.h"
      40                 :            : #include "qgssymbollayer.h"
      41                 :            : #include "qgsvectorlayerutils.h"
      42                 :            : #include "qgssymbollayerutils.h"
      43                 :            : #include "qgsmaplayertemporalproperties.h"
      44                 :            : #include "qgsmaplayerelevationproperties.h"
      45                 :            : #include "qgsvectorlayerrenderer.h"
      46                 :            : 
      47                 :            : ///@cond PRIVATE
      48                 :            : 
      49                 :         10 : const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
      50                 :         10 : const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
      51                 :            : 
      52                 :          0 : bool LayerRenderJob::imageCanBeComposed() const
      53                 :            : {
      54                 :          0 :   if ( imageInitialized )
      55                 :            :   {
      56                 :          0 :     if ( renderer )
      57                 :            :     {
      58                 :          0 :       return renderer->isReadyToCompose();
      59                 :            :     }
      60                 :            :     else
      61                 :            :     {
      62                 :          0 :       return true;
      63                 :            :     }
      64                 :            :   }
      65                 :            :   else
      66                 :            :   {
      67                 :          0 :     return false;
      68                 :            :   }
      69                 :          0 : }
      70                 :            : 
      71                 :          0 : QgsMapRendererJob::QgsMapRendererJob( const QgsMapSettings &settings )
      72                 :          0 :   : mSettings( settings )
      73                 :            : 
      74                 :          0 : {
      75                 :          0 : }
      76                 :            : 
      77                 :            : 
      78                 :          0 : QgsMapRendererQImageJob::QgsMapRendererQImageJob( const QgsMapSettings &settings )
      79                 :          0 :   : QgsMapRendererJob( settings )
      80                 :          0 : {
      81                 :          0 : }
      82                 :            : 
      83                 :            : 
      84                 :          0 : QgsMapRendererJob::Errors QgsMapRendererJob::errors() const
      85                 :            : {
      86                 :          0 :   return mErrors;
      87                 :            : }
      88                 :            : 
      89                 :          0 : void QgsMapRendererJob::setCache( QgsMapRendererCache *cache )
      90                 :            : {
      91                 :          0 :   mCache = cache;
      92                 :          0 : }
      93                 :            : 
      94                 :          0 : QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
      95                 :            : {
      96                 :          0 :   QHash<QgsMapLayer *, int> result;
      97                 :          0 :   for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
      98                 :            :   {
      99                 :          0 :     if ( auto &&lKey = it.key() )
     100                 :          0 :       result.insert( lKey, it.value() );
     101                 :          0 :   }
     102                 :          0 :   return result;
     103                 :          0 : }
     104                 :            : 
     105                 :          0 : void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
     106                 :            : {
     107                 :          0 :   mLayerRenderingTimeHints = hints;
     108                 :          0 : }
     109                 :            : 
     110                 :          0 : const QgsMapSettings &QgsMapRendererJob::mapSettings() const
     111                 :            : {
     112                 :          0 :   return mSettings;
     113                 :            : }
     114                 :            : 
     115                 :          0 : bool QgsMapRendererJob::prepareLabelCache() const
     116                 :            : {
     117                 :          0 :   bool canCache = mCache;
     118                 :            : 
     119                 :            :   // calculate which layers will be labeled
     120                 :          0 :   QSet< QgsMapLayer * > labeledLayers;
     121                 :          0 :   const QList<QgsMapLayer *> layers = mSettings.layers();
     122                 :          0 :   for ( QgsMapLayer *ml : layers )
     123                 :            :   {
     124                 :          0 :     if ( QgsPalLabeling::staticWillUseLayer( ml ) )
     125                 :          0 :       labeledLayers << ml;
     126                 :            : 
     127                 :          0 :     switch ( ml->type() )
     128                 :            :     {
     129                 :            :       case QgsMapLayerType::VectorLayer:
     130                 :            :       {
     131                 :          0 :         QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
     132                 :          0 :         if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
     133                 :            :         {
     134                 :          0 :           canCache = false;
     135                 :          0 :         }
     136                 :          0 :         break;
     137                 :            :       }
     138                 :            : 
     139                 :            :       case QgsMapLayerType::VectorTileLayer:
     140                 :            :       {
     141                 :            :         // TODO -- add detection of advanced labeling effects for vector tile layers
     142                 :          0 :         break;
     143                 :            :       }
     144                 :            : 
     145                 :            :       case QgsMapLayerType::RasterLayer:
     146                 :            :       case QgsMapLayerType::AnnotationLayer:
     147                 :            :       case QgsMapLayerType::PluginLayer:
     148                 :            :       case QgsMapLayerType::MeshLayer:
     149                 :            :       case QgsMapLayerType::PointCloudLayer:
     150                 :          0 :         break;
     151                 :            :     }
     152                 :            : 
     153                 :          0 :     if ( !canCache )
     154                 :          0 :       break;
     155                 :            : 
     156                 :            :   }
     157                 :            : 
     158                 :          0 :   if ( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) )
     159                 :            :   {
     160                 :            :     // we may need to clear label cache and re-register labeled features - check for that here
     161                 :            : 
     162                 :            :     // can we reuse the cached label solution?
     163                 :          0 :     bool canUseCache = canCache && qgis::listToSet( mCache->dependentLayers( LABEL_CACHE_ID ) ) == labeledLayers;
     164                 :          0 :     if ( !canUseCache )
     165                 :            :     {
     166                 :            :       // no - participating layers have changed
     167                 :          0 :       mCache->clearCacheImage( LABEL_CACHE_ID );
     168                 :          0 :     }
     169                 :          0 :   }
     170                 :          0 :   return canCache;
     171                 :          0 : }
     172                 :            : 
     173                 :            : 
     174                 :          0 : bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
     175                 :            : {
     176                 :          0 :   bool res = true;
     177                 :            :   // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
     178                 :            :   // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
     179                 :          0 :   QgsCoordinateTransform approxTransform = ct;
     180                 :          0 :   approxTransform.setBallparkTransformsAreAppropriate( true );
     181                 :            : 
     182                 :            :   try
     183                 :            :   {
     184                 :            : #ifdef QGISDEBUG
     185                 :            :     // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
     186                 :            : #endif
     187                 :            :     // Split the extent into two if the source CRS is
     188                 :            :     // geographic and the extent crosses the split in
     189                 :            :     // geographic coordinates (usually +/- 180 degrees,
     190                 :            :     // and is assumed to be so here), and draw each
     191                 :            :     // extent separately.
     192                 :            :     static const double SPLIT_COORD = 180.0;
     193                 :            : 
     194                 :          0 :     if ( ml->crs().isGeographic() )
     195                 :            :     {
     196                 :          0 :       if ( ml->type() == QgsMapLayerType::VectorLayer && !approxTransform.destinationCrs().isGeographic() )
     197                 :            :       {
     198                 :            :         // if we transform from a projected coordinate system check
     199                 :            :         // check if transforming back roughly returns the input
     200                 :            :         // extend - otherwise render the world.
     201                 :          0 :         QgsRectangle extent1 = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
     202                 :          0 :         QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, QgsCoordinateTransform::ForwardTransform );
     203                 :            : 
     204                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
     205                 :            :                           .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
     206                 :            :                           .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
     207                 :            :                           .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
     208                 :            :                           .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
     209                 :            :                           , 3 );
     210                 :            : 
     211                 :            :         // can differ by a maximum of up to 20% of height/width
     212                 :          0 :         if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
     213                 :          0 :              && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
     214                 :          0 :              && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
     215                 :          0 :              && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
     216                 :            :            )
     217                 :            :         {
     218                 :          0 :           extent = extent1;
     219                 :          0 :         }
     220                 :            :         else
     221                 :            :         {
     222                 :          0 :           extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
     223                 :          0 :           res = false;
     224                 :            :         }
     225                 :          0 :       }
     226                 :            :       else
     227                 :            :       {
     228                 :            :         // Note: ll = lower left point
     229                 :          0 :         QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
     230                 :            :                         QgsCoordinateTransform::ReverseTransform );
     231                 :            : 
     232                 :            :         //   and ur = upper right point
     233                 :          0 :         QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
     234                 :            :                         QgsCoordinateTransform::ReverseTransform );
     235                 :            : 
     236                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
     237                 :            : 
     238                 :          0 :         extent = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
     239                 :            : 
     240                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
     241                 :            : 
     242                 :          0 :         if ( ll.x() > ur.x() )
     243                 :            :         {
     244                 :            :           // the coordinates projected in reverse order than what one would expect.
     245                 :            :           // we are probably looking at an area that includes longitude of 180 degrees.
     246                 :            :           // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
     247                 :            :           // so let's use (-180,180). This hopefully does not add too much overhead. It is
     248                 :            :           // more straightforward than rendering with two separate extents and more consistent
     249                 :            :           // for rendering, labeling and caching as everything is rendered just in one go
     250                 :          0 :           extent.setXMinimum( -SPLIT_COORD );
     251                 :          0 :           extent.setXMaximum( SPLIT_COORD );
     252                 :          0 :           res = false;
     253                 :          0 :         }
     254                 :            :       }
     255                 :            : 
     256                 :            :       // TODO: the above rule still does not help if using a projection that covers the whole
     257                 :            :       // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
     258                 :            :       // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
     259                 :            :       // but in fact the extent should cover the whole world.
     260                 :          0 :     }
     261                 :            :     else // can't cross 180
     262                 :            :     {
     263                 :          0 :       if ( approxTransform.destinationCrs().isGeographic() &&
     264                 :          0 :            ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
     265                 :          0 :              extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
     266                 :            :         // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
     267                 :            :         // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
     268                 :            :         // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
     269                 :            :         // but this seems like a safer choice.
     270                 :            :       {
     271                 :          0 :         extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
     272                 :          0 :         res = false;
     273                 :          0 :       }
     274                 :            :       else
     275                 :          0 :         extent = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
     276                 :            :     }
     277                 :          0 :   }
     278                 :            :   catch ( QgsCsException & )
     279                 :            :   {
     280                 :          0 :     QgsDebugMsg( QStringLiteral( "Transform error caught" ) );
     281                 :          0 :     extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
     282                 :          0 :     r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
     283                 :          0 :     res = false;
     284                 :          0 :   }
     285                 :            : 
     286                 :          0 :   return res;
     287                 :          0 : }
     288                 :            : 
     289                 :          0 : QImage *QgsMapRendererJob::allocateImage( QString layerId )
     290                 :            : {
     291                 :          0 :   QImage *image = new QImage( mSettings.deviceOutputSize(),
     292                 :          0 :                               mSettings.outputImageFormat() );
     293                 :          0 :   image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
     294                 :          0 :   if ( image->isNull() )
     295                 :            :   {
     296                 :          0 :     mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
     297                 :          0 :     delete image;
     298                 :          0 :     return nullptr;
     299                 :            :   }
     300                 :          0 :   return image;
     301                 :          0 : }
     302                 :            : 
     303                 :          0 : QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image )
     304                 :            : {
     305                 :          0 :   QPainter *painter = nullptr;
     306                 :          0 :   image = allocateImage( layerId );
     307                 :          0 :   if ( image )
     308                 :            :   {
     309                 :          0 :     painter = new QPainter( image );
     310                 :          0 :     painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
     311                 :            : #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
     312                 :          0 :     painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
     313                 :            : #endif
     314                 :          0 :   }
     315                 :          0 :   return painter;
     316                 :          0 : }
     317                 :            : 
     318                 :          0 : LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
     319                 :            : {
     320                 :          0 :   LayerRenderJobs layerJobs;
     321                 :            : 
     322                 :            :   // render all layers in the stack, starting at the base
     323                 :          0 :   QListIterator<QgsMapLayer *> li( mSettings.layers() );
     324                 :          0 :   li.toBack();
     325                 :            : 
     326                 :          0 :   if ( mCache )
     327                 :            :   {
     328                 :          0 :     bool cacheValid = mCache->updateParameters( mSettings.visibleExtent(), mSettings.mapToPixel() );
     329                 :            :     Q_UNUSED( cacheValid )
     330                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
     331                 :          0 :   }
     332                 :            : 
     333                 :          0 :   bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
     334                 :            : 
     335                 :          0 :   while ( li.hasPrevious() )
     336                 :            :   {
     337                 :          0 :     QgsMapLayer *ml = li.previous();
     338                 :            : 
     339                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "layer %1:  minscale:%2  maxscale:%3  scaledepvis:%4  blendmode:%5 isValid:%6" )
     340                 :            :                       .arg( ml->name() )
     341                 :            :                       .arg( ml->minimumScale() )
     342                 :            :                       .arg( ml->maximumScale() )
     343                 :            :                       .arg( ml->hasScaleBasedVisibility() )
     344                 :            :                       .arg( ml->blendMode() )
     345                 :            :                       .arg( ml->isValid() )
     346                 :            :                       , 3 );
     347                 :            : 
     348                 :          0 :     if ( !ml->isValid() )
     349                 :            :     {
     350                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
     351                 :          0 :       continue;
     352                 :            :     }
     353                 :            : 
     354                 :          0 :     if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
     355                 :            :     {
     356                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
     357                 :          0 :       continue;
     358                 :            :     }
     359                 :            : 
     360                 :          0 :     if ( mSettings.isTemporal() && ml->temporalProperties() && !ml->temporalProperties()->isVisibleInTemporalRange( mSettings.temporalRange() ) )
     361                 :            :     {
     362                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
     363                 :          0 :       continue;
     364                 :            :     }
     365                 :            : 
     366                 :          0 :     if ( !mSettings.zRange().isInfinite() && ml->elevationProperties() && !ml->elevationProperties()->isVisibleInZRange( mSettings.zRange() ) )
     367                 :            :     {
     368                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
     369                 :          0 :       continue;
     370                 :            :     }
     371                 :            : 
     372                 :          0 :     QgsRectangle r1 = mSettings.visibleExtent(), r2;
     373                 :          0 :     r1.grow( mSettings.extentBuffer() );
     374                 :          0 :     QgsCoordinateTransform ct;
     375                 :            : 
     376                 :          0 :     ct = mSettings.layerTransform( ml );
     377                 :          0 :     bool haveExtentInLayerCrs = true;
     378                 :          0 :     if ( ct.isValid() )
     379                 :            :     {
     380                 :          0 :       haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
     381                 :          0 :     }
     382                 :          0 :     QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
     383                 :          0 :     if ( !r1.isFinite() || !r2.isFinite() )
     384                 :            :     {
     385                 :          0 :       mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
     386                 :          0 :       continue;
     387                 :            :     }
     388                 :            : 
     389                 :          0 :     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
     390                 :            : 
     391                 :            :     // Force render of layers that are being edited
     392                 :            :     // or if there's a labeling engine that needs the layer to register features
     393                 :          0 :     if ( mCache )
     394                 :            :     {
     395                 :          0 :       const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
     396                 :          0 :       if ( ( vl && vl->isEditable() ) || requiresLabeling )
     397                 :            :       {
     398                 :          0 :         mCache->clearCacheImage( ml->id() );
     399                 :          0 :       }
     400                 :          0 :     }
     401                 :            : 
     402                 :          0 :     layerJobs.append( LayerRenderJob() );
     403                 :          0 :     LayerRenderJob &job = layerJobs.last();
     404                 :          0 :     job.cached = false;
     405                 :          0 :     job.img = nullptr;
     406                 :          0 :     job.layer = ml;
     407                 :          0 :     job.layerId = ml->id();
     408                 :          0 :     job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
     409                 :          0 :     job.renderingTime = -1;
     410                 :            : 
     411                 :          0 :     job.context = QgsRenderContext::fromMapSettings( mSettings );
     412                 :          0 :     job.context.expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
     413                 :          0 :     job.context.setPainter( painter );
     414                 :          0 :     job.context.setLabelingEngine( labelingEngine2 );
     415                 :          0 :     job.context.setCoordinateTransform( ct );
     416                 :          0 :     job.context.setExtent( r1 );
     417                 :          0 :     if ( !haveExtentInLayerCrs )
     418                 :          0 :       job.context.setFlag( QgsRenderContext::ApplyClipAfterReprojection, true );
     419                 :            : 
     420                 :          0 :     if ( mFeatureFilterProvider )
     421                 :          0 :       job.context.setFeatureFilterProvider( mFeatureFilterProvider );
     422                 :            : 
     423                 :          0 :     QgsMapLayerStyleOverride styleOverride( ml );
     424                 :          0 :     if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
     425                 :          0 :       styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
     426                 :            : 
     427                 :          0 :     job.blendMode = ml->blendMode();
     428                 :            : 
     429                 :            :     // raster layer opacity is handled directly within the raster layer renderer, so don't
     430                 :            :     // apply default opacity handling here!
     431                 :          0 :     job.opacity = ml->type() != QgsMapLayerType::RasterLayer ? ml->opacity() : 1.0;
     432                 :            : 
     433                 :            :     // if we can use the cache, let's do it and avoid rendering!
     434                 :          0 :     if ( mCache && mCache->hasCacheImage( ml->id() ) )
     435                 :            :     {
     436                 :          0 :       job.cached = true;
     437                 :          0 :       job.imageInitialized = true;
     438                 :          0 :       job.img = new QImage( mCache->cacheImage( ml->id() ) );
     439                 :          0 :       job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
     440                 :          0 :       job.renderer = nullptr;
     441                 :          0 :       job.context.setPainter( nullptr );
     442                 :          0 :       continue;
     443                 :            :     }
     444                 :            : 
     445                 :          0 :     QElapsedTimer layerTime;
     446                 :          0 :     layerTime.start();
     447                 :          0 :     job.renderer = ml->createMapRenderer( job.context );
     448                 :          0 :     if ( job.renderer )
     449                 :          0 :       job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
     450                 :            : 
     451                 :            :     // If we are drawing with an alternative blending mode then we need to render to a separate image
     452                 :            :     // before compositing this on the map. This effectively flattens the layer and prevents
     453                 :            :     // blending occurring between objects on the layer
     454                 :          0 :     if ( mCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
     455                 :            :     {
     456                 :            :       // Flattened image for drawing when a blending mode is set
     457                 :          0 :       job.context.setPainter( allocateImageAndPainter( ml->id(), job.img ) );
     458                 :          0 :       if ( ! job.img )
     459                 :            :       {
     460                 :          0 :         delete job.renderer;
     461                 :          0 :         job.renderer = nullptr;
     462                 :          0 :         layerJobs.removeLast();
     463                 :          0 :         continue;
     464                 :            :       }
     465                 :          0 :     }
     466                 :            : 
     467                 :          0 :     job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
     468                 :          0 :   }
     469                 :            : 
     470                 :          0 :   return layerJobs;
     471                 :          0 : }
     472                 :            : 
     473                 :          0 : LayerRenderJobs QgsMapRendererJob::prepareSecondPassJobs( LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob )
     474                 :            : {
     475                 :          0 :   LayerRenderJobs secondPassJobs;
     476                 :            : 
     477                 :            :   // We will need to quickly access the associated rendering job of a layer
     478                 :          0 :   QHash<QString, LayerRenderJob *> layerJobMapping;
     479                 :            : 
     480                 :          0 :   // ... and whether a layer has a mask defined
     481                 :          0 :   QSet<QString> layerHasMask;
     482                 :            : 
     483                 :          0 :   struct MaskSource
     484                 :            :   {
     485                 :            :     QString layerId;
     486                 :            :     QString labelRuleId;
     487                 :            :     int labelMaskId;
     488                 :          0 :     MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_ ):
     489                 :          0 :       layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ) {}
     490                 :            :   };
     491                 :            : 
     492                 :            :   // We collect for each layer, the set of symbol layers that will be "masked"
     493                 :            :   // and the list of source layers that have a mask
     494                 :          0 :   QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
     495                 :            : 
     496                 :          0 :   for ( LayerRenderJob &job : firstPassJobs )
     497                 :            :   {
     498                 :          0 :     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
     499                 :          0 :     if ( ! vl )
     500                 :          0 :       continue;
     501                 :            : 
     502                 :          0 :     layerJobMapping[job.layer->id()] = &job;
     503                 :            : 
     504                 :            :     // lambda function to factor code for both label masks and symbol layer masks
     505                 :          0 :     auto collectMasks = [&]( QHash<QString, QSet<QgsSymbolLayerId>> *masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
     506                 :            :     {
     507                 :          0 :       for ( auto it = masks->begin(); it != masks->end(); ++it )
     508                 :            :       {
     509                 :          0 :         auto lit = maskedSymbolLayers.find( it.key() );
     510                 :          0 :         if ( lit == maskedSymbolLayers.end() )
     511                 :            :         {
     512                 :          0 :           maskedSymbolLayers[it.key()] = qMakePair( it.value(), QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId ) );
     513                 :          0 :         }
     514                 :            :         else
     515                 :            :         {
     516                 :          0 :           if ( lit->first != it.value() )
     517                 :            :           {
     518                 :          0 :             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() ) );
     519                 :          0 :             continue;
     520                 :            :           }
     521                 :          0 :           lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId ) );
     522                 :            :         }
     523                 :          0 :       }
     524                 :          0 :       if ( ! masks->isEmpty() )
     525                 :          0 :         layerHasMask.insert( sourceLayerId );
     526                 :          0 :     };
     527                 :            : 
     528                 :            :     // collect label masks
     529                 :          0 :     QHash<QString, QHash<QString, QSet<QgsSymbolLayerId>>> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
     530                 :          0 :     for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
     531                 :            :     {
     532                 :          0 :       QString labelRule = it.key();
     533                 :          0 :       QHash<QString, QSet<QgsSymbolLayerId>> masks = it.value();
     534                 :            : 
     535                 :            :       // group layers by QSet<QgsSymbolLayerReference>
     536                 :          0 :       QSet<QgsSymbolLayerReference> slRefs;
     537                 :          0 :       for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
     538                 :            :       {
     539                 :          0 :         for ( auto slIt = mit.value().begin(); slIt != mit.value().end(); slIt++ )
     540                 :            :         {
     541                 :          0 :           slRefs.insert( QgsSymbolLayerReference( mit.key(), *slIt ) );
     542                 :          0 :         }
     543                 :          0 :       }
     544                 :            :       // generate a new mask id for this set
     545                 :          0 :       int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
     546                 :            : 
     547                 :            :       // now collect masks
     548                 :          0 :       collectMasks( &masks, vl->id(), labelRule, labelMaskId );
     549                 :          0 :     }
     550                 :            : 
     551                 :            :     // collect symbol layer masks
     552                 :          0 :     QHash<QString, QSet<QgsSymbolLayerId>> symbolLayerMasks = QgsVectorLayerUtils::symbolLayerMasks( vl );
     553                 :          0 :     collectMasks( &symbolLayerMasks, vl->id() );
     554                 :          0 :   }
     555                 :            : 
     556                 :          0 :   if ( maskedSymbolLayers.isEmpty() )
     557                 :          0 :     return secondPassJobs;
     558                 :            : 
     559                 :            :   // Now that we know some layers have a mask, we have to allocate a mask image and painter
     560                 :            :   // for them in the first pass job
     561                 :          0 :   for ( LayerRenderJob &job : firstPassJobs )
     562                 :            :   {
     563                 :          0 :     QgsMapLayer *ml = job.layer;
     564                 :            : 
     565                 :          0 :     if ( job.img == nullptr )
     566                 :            :     {
     567                 :          0 :       job.context.setPainter( allocateImageAndPainter( ml->id(), job.img ) );
     568                 :          0 :     }
     569                 :          0 :     if ( layerHasMask.contains( ml->id() ) )
     570                 :            :     {
     571                 :            :       // Note: we only need an alpha channel here, rather than a full RGBA image
     572                 :          0 :       job.context.setMaskPainter( allocateImageAndPainter( ml->id(), job.maskImage ) );
     573                 :          0 :       job.maskImage->fill( 0 );
     574                 :          0 :     }
     575                 :            :   }
     576                 :            : 
     577                 :            :   // Allocate an image for labels
     578                 :          0 :   if ( labelJob.img == nullptr )
     579                 :            :   {
     580                 :          0 :     labelJob.img = allocateImage( QStringLiteral( "labels" ) );
     581                 :          0 :   }
     582                 :            : 
     583                 :            :   // Prepare label mask images
     584                 :          0 :   for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
     585                 :            :   {
     586                 :            :     QImage *maskImage;
     587                 :          0 :     labelJob.context.setMaskPainter( allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage ), maskId );
     588                 :          0 :     maskImage->fill( 0 );
     589                 :          0 :     labelJob.maskImages.push_back( maskImage );
     590                 :          0 :   }
     591                 :          0 :   labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
     592                 :            : 
     593                 :            :   // Prepare second pass jobs
     594                 :          0 :   for ( LayerRenderJob &job : firstPassJobs )
     595                 :            :   {
     596                 :          0 :     QgsMapLayer *ml = job.layer;
     597                 :            : 
     598                 :          0 :     auto it = maskedSymbolLayers.find( ml->id() );
     599                 :          0 :     if ( it == maskedSymbolLayers.end() )
     600                 :          0 :       continue;
     601                 :            : 
     602                 :          0 :     QList<MaskSource> &sourceList = it->second;
     603                 :          0 :     const QSet<QgsSymbolLayerId> &symbolList = it->first;
     604                 :            : 
     605                 :            :     // copy the initial job ...
     606                 :          0 :     secondPassJobs.append( LayerRenderJob() );
     607                 :          0 :     LayerRenderJob &job2 = secondPassJobs.last();
     608                 :          0 :     job2 = job;
     609                 :          0 :     job2.cached = false;
     610                 :          0 :     job2.firstPassJob = &job;
     611                 :          0 :     QgsVectorLayer *vl1 = qobject_cast<QgsVectorLayer *>( job.layer );
     612                 :            : 
     613                 :            :     // ... but clear the image
     614                 :          0 :     job2.context.setMaskPainter( nullptr );
     615                 :          0 :     job2.context.setPainter( allocateImageAndPainter( vl1->id(), job2.img ) );
     616                 :          0 :     if ( ! job2.img )
     617                 :            :     {
     618                 :          0 :       secondPassJobs.removeLast();
     619                 :          0 :       continue;
     620                 :            :     }
     621                 :            : 
     622                 :            :     // Points to the first pass job. This will be needed during the second pass composition.
     623                 :          0 :     for ( MaskSource &source : sourceList )
     624                 :            :     {
     625                 :          0 :       if ( source.labelMaskId != -1 )
     626                 :          0 :         job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
     627                 :            :       else
     628                 :          0 :         job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
     629                 :            :     }
     630                 :            : 
     631                 :            :     // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
     632                 :            :     // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
     633                 :          0 :     QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( vl1->createMapRenderer( job2.context ) );
     634                 :          0 :     job2.renderer = mapRenderer;
     635                 :            : 
     636                 :            :     // Modify the render context so that symbol layers get disabled as needed.
     637                 :            :     // The map renderer stores a reference to the context, so we can modify it even after the map renderer creation (what we need here)
     638                 :          0 :     job2.context.setDisabledSymbolLayers( QgsSymbolLayerUtils::toSymbolLayerPointers( mapRenderer->featureRenderer(), symbolList ) );
     639                 :            :   }
     640                 :            : 
     641                 :          0 :   return secondPassJobs;
     642                 :          0 : }
     643                 :            : 
     644                 :          0 : LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
     645                 :            : {
     646                 :          0 :   LabelRenderJob job;
     647                 :          0 :   job.context = QgsRenderContext::fromMapSettings( mSettings );
     648                 :          0 :   job.context.setPainter( painter );
     649                 :          0 :   job.context.setLabelingEngine( labelingEngine2 );
     650                 :          0 :   job.context.setExtent( mSettings.visibleExtent() );
     651                 :          0 :   job.context.setFeatureFilterProvider( mFeatureFilterProvider );
     652                 :          0 :   QgsCoordinateTransform ct;
     653                 :          0 :   ct.setDestinationCrs( mSettings.destinationCrs() );
     654                 :          0 :   job.context.setCoordinateTransform( ct );
     655                 :            : 
     656                 :            :   // if we can use the cache, let's do it and avoid rendering!
     657                 :          0 :   bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
     658                 :          0 :   if ( hasCache )
     659                 :            :   {
     660                 :          0 :     job.cached = true;
     661                 :          0 :     job.complete = true;
     662                 :          0 :     job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
     663                 :            :     Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
     664                 :          0 :     job.context.setPainter( nullptr );
     665                 :          0 :   }
     666                 :            :   else
     667                 :            :   {
     668                 :          0 :     if ( canUseLabelCache && ( mCache || !painter ) )
     669                 :            :     {
     670                 :          0 :       job.img = allocateImage( QStringLiteral( "labels" ) );
     671                 :          0 :     }
     672                 :            :   }
     673                 :            : 
     674                 :          0 :   return job;
     675                 :          0 : }
     676                 :            : 
     677                 :            : 
     678                 :          0 : void QgsMapRendererJob::cleanupJobs( LayerRenderJobs &jobs )
     679                 :            : {
     680                 :          0 :   for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
     681                 :            :   {
     682                 :          0 :     LayerRenderJob &job = *it;
     683                 :          0 :     if ( job.img )
     684                 :            :     {
     685                 :          0 :       delete job.context.painter();
     686                 :          0 :       job.context.setPainter( nullptr );
     687                 :            : 
     688                 :          0 :       if ( mCache && !job.cached && job.completed && job.layer )
     689                 :            :       {
     690                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
     691                 :          0 :         mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
     692                 :          0 :         mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
     693                 :          0 :       }
     694                 :            : 
     695                 :          0 :       delete job.img;
     696                 :          0 :       job.img = nullptr;
     697                 :          0 :     }
     698                 :            : 
     699                 :            :     // delete the mask image and painter
     700                 :          0 :     if ( job.maskImage )
     701                 :            :     {
     702                 :          0 :       delete job.context.maskPainter();
     703                 :          0 :       job.context.setMaskPainter( nullptr );
     704                 :          0 :       delete job.maskImage;
     705                 :          0 :     }
     706                 :            : 
     707                 :          0 :     if ( job.renderer )
     708                 :            :     {
     709                 :          0 :       const auto constErrors = job.renderer->errors();
     710                 :          0 :       for ( const QString &message : constErrors )
     711                 :          0 :         mErrors.append( Error( job.renderer->layerId(), message ) );
     712                 :            : 
     713                 :          0 :       delete job.renderer;
     714                 :          0 :       job.renderer = nullptr;
     715                 :          0 :     }
     716                 :            : 
     717                 :          0 :     if ( job.layer )
     718                 :          0 :       mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
     719                 :          0 :   }
     720                 :            : 
     721                 :          0 :   jobs.clear();
     722                 :          0 : }
     723                 :            : 
     724                 :          0 : void QgsMapRendererJob::cleanupSecondPassJobs( LayerRenderJobs &jobs )
     725                 :            : {
     726                 :          0 :   for ( auto &job : jobs )
     727                 :            :   {
     728                 :          0 :     if ( job.img )
     729                 :            :     {
     730                 :          0 :       delete job.context.painter();
     731                 :          0 :       job.context.setPainter( nullptr );
     732                 :            : 
     733                 :          0 :       delete job.img;
     734                 :          0 :       job.img = nullptr;
     735                 :          0 :     }
     736                 :            : 
     737                 :          0 :     if ( job.renderer )
     738                 :            :     {
     739                 :          0 :       delete job.renderer;
     740                 :          0 :       job.renderer = nullptr;
     741                 :          0 :     }
     742                 :            : 
     743                 :          0 :     if ( job.layer )
     744                 :          0 :       mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
     745                 :            :   }
     746                 :            : 
     747                 :          0 :   jobs.clear();
     748                 :          0 : }
     749                 :            : 
     750                 :          0 : void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
     751                 :            : {
     752                 :          0 :   if ( job.img )
     753                 :            :   {
     754                 :          0 :     if ( mCache && !job.cached && !job.context.renderingStopped() )
     755                 :            :     {
     756                 :          0 :       QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
     757                 :          0 :       mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
     758                 :          0 :       mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
     759                 :          0 :     }
     760                 :            : 
     761                 :          0 :     delete job.img;
     762                 :          0 :     job.img = nullptr;
     763                 :          0 :   }
     764                 :            : 
     765                 :          0 :   for ( int maskId = 0; maskId < job.maskImages.size(); maskId++ )
     766                 :            :   {
     767                 :          0 :     delete job.context.maskPainter( maskId );
     768                 :          0 :     job.context.setMaskPainter( nullptr, maskId );
     769                 :          0 :     delete job.maskImages[maskId];
     770                 :          0 :   }
     771                 :          0 : }
     772                 :            : 
     773                 :            : 
     774                 :            : #define DEBUG_RENDERING 0
     775                 :            : 
     776                 :          0 : QImage QgsMapRendererJob::composeImage(
     777                 :            :   const QgsMapSettings &settings,
     778                 :            :   const LayerRenderJobs &jobs,
     779                 :            :   const LabelRenderJob &labelJob,
     780                 :            :   const QgsMapRendererCache *cache
     781                 :            : )
     782                 :            : {
     783                 :          0 :   QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
     784                 :          0 :   image.setDevicePixelRatio( settings.devicePixelRatio() );
     785                 :          0 :   image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
     786                 :          0 :   image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
     787                 :          0 :   image.fill( settings.backgroundColor().rgba() );
     788                 :            : 
     789                 :          0 :   QPainter painter( &image );
     790                 :            : 
     791                 :            : #if DEBUG_RENDERING
     792                 :            :   int i = 0;
     793                 :            : #endif
     794                 :          0 :   for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
     795                 :            :   {
     796                 :          0 :     const LayerRenderJob &job = *it;
     797                 :            : 
     798                 :          0 :     if ( job.layer && job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
     799                 :          0 :       continue; // skip layer for now, it will be rendered after labels
     800                 :            : 
     801                 :          0 :     QImage img = layerImageToBeComposed( settings, job, cache );
     802                 :          0 :     if ( img.isNull() )
     803                 :          0 :       continue; // image is not prepared and not even in cache
     804                 :            : 
     805                 :          0 :     painter.setCompositionMode( job.blendMode );
     806                 :          0 :     painter.setOpacity( job.opacity );
     807                 :            : 
     808                 :            : #if DEBUG_RENDERING
     809                 :            :     img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
     810                 :            :     i++;
     811                 :            : #endif
     812                 :            : 
     813                 :          0 :     painter.drawImage( 0, 0, img );
     814                 :          0 :   }
     815                 :            : 
     816                 :            :   // IMPORTANT - don't draw labelJob img before the label job is complete,
     817                 :            :   // as the image is uninitialized and full of garbage before the label job
     818                 :            :   // commences
     819                 :          0 :   if ( labelJob.img && labelJob.complete )
     820                 :            :   {
     821                 :          0 :     painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
     822                 :          0 :     painter.setOpacity( 1.0 );
     823                 :          0 :     painter.drawImage( 0, 0, *labelJob.img );
     824                 :          0 :   }
     825                 :            :   // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
     826                 :            :   // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
     827                 :            :   // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
     828                 :          0 :   else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
     829                 :            :   {
     830                 :          0 :     const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
     831                 :          0 :     painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
     832                 :          0 :     painter.setOpacity( 1.0 );
     833                 :          0 :     painter.drawImage( 0, 0, labelCacheImage );
     834                 :          0 :   }
     835                 :            : 
     836                 :            :   // render any layers with the renderAboveLabels flag now
     837                 :          0 :   for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
     838                 :            :   {
     839                 :          0 :     const LayerRenderJob &job = *it;
     840                 :            : 
     841                 :          0 :     if ( !job.layer || !job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
     842                 :          0 :       continue;
     843                 :            : 
     844                 :          0 :     QImage img = layerImageToBeComposed( settings, job, cache );
     845                 :          0 :     if ( img.isNull() )
     846                 :          0 :       continue; // image is not prepared and not even in cache
     847                 :            : 
     848                 :          0 :     painter.setCompositionMode( job.blendMode );
     849                 :          0 :     painter.setOpacity( job.opacity );
     850                 :            : 
     851                 :          0 :     painter.drawImage( 0, 0, img );
     852                 :          0 :   }
     853                 :            : 
     854                 :          0 :   painter.end();
     855                 :            : #if DEBUG_RENDERING
     856                 :            :   image.save( "/tmp/final.png" );
     857                 :            : #endif
     858                 :          0 :   return image;
     859                 :          0 : }
     860                 :            : 
     861                 :          0 : QImage QgsMapRendererJob::layerImageToBeComposed(
     862                 :            :   const QgsMapSettings &settings,
     863                 :            :   const LayerRenderJob &job,
     864                 :            :   const QgsMapRendererCache *cache
     865                 :            : )
     866                 :            : {
     867                 :          0 :   if ( job.imageCanBeComposed() )
     868                 :            :   {
     869                 :            :     Q_ASSERT( job.img );
     870                 :          0 :     return *job.img;
     871                 :            :   }
     872                 :            :   else
     873                 :            :   {
     874                 :          0 :     if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
     875                 :            :     {
     876                 :          0 :       return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
     877                 :            :     }
     878                 :            :     else
     879                 :          0 :       return QImage();
     880                 :            :   }
     881                 :          0 : }
     882                 :            : 
     883                 :          0 : void QgsMapRendererJob::composeSecondPass( LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob )
     884                 :            : {
     885                 :            : #if DEBUG_RENDERING
     886                 :            :   int i = 0;
     887                 :            : #endif
     888                 :            :   // compose the second pass with the mask
     889                 :          0 :   for ( LayerRenderJob &job : secondPassJobs )
     890                 :            :   {
     891                 :            : #if DEBUG_RENDERING
     892                 :            :     i++;
     893                 :            :     job.img->save( QString( "/tmp/second_%1.png" ).arg( i ) );
     894                 :            :     int mask = 0;
     895                 :            : #endif
     896                 :            : 
     897                 :            :     // Merge all mask images into the first one if we have more than one mask image
     898                 :          0 :     if ( job.maskJobs.size() > 1 )
     899                 :            :     {
     900                 :          0 :       QPainter *maskPainter = nullptr;
     901                 :          0 :       for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
     902                 :            :       {
     903                 :          0 :         QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
     904                 :            : #if DEBUG_RENDERING
     905                 :            :         maskImage->save( QString( "/tmp/mask_%1_%2.png" ).arg( i ).arg( mask++ ) );
     906                 :            : #endif
     907                 :          0 :         if ( ! maskPainter )
     908                 :            :         {
     909                 :          0 :           maskPainter = p.first ? p.first->context.maskPainter() : labelJob.context.maskPainter( p.second );
     910                 :          0 :         }
     911                 :            :         else
     912                 :            :         {
     913                 :          0 :           maskPainter->drawImage( 0, 0, *maskImage );
     914                 :            :         }
     915                 :            :       }
     916                 :          0 :     }
     917                 :            : 
     918                 :          0 :     if ( ! job.maskJobs.isEmpty() )
     919                 :            :     {
     920                 :            :       // All have been merged into the first
     921                 :          0 :       QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
     922                 :          0 :       QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
     923                 :            : #if DEBUG_RENDERING
     924                 :            :       maskImage->save( QString( "/tmp/mask_%1.png" ).arg( i ) );
     925                 :            : #endif
     926                 :            : 
     927                 :            :       // Only retain parts of the second rendering that are "inside" the mask image
     928                 :          0 :       QPainter *painter = job.context.painter();
     929                 :          0 :       painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
     930                 :            : 
     931                 :            :       //Create an "alpha binarized" image of the maskImage to :
     932                 :            :       //* Eliminate antialiasing artifact
     933                 :            :       //* Avoid applying mask opacity to elements under the mask but not masked
     934                 :          0 :       QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
     935                 :          0 :       QVector<QRgb> mswTable;
     936                 :          0 :       mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
     937                 :          0 :       mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
     938                 :          0 :       maskBinAlpha.setColorTable( mswTable );
     939                 :          0 :       painter->drawImage( 0, 0, maskBinAlpha );
     940                 :            : #if DEBUG_RENDERING
     941                 :            :       job.img->save( QString( "/tmp/second_%1_a.png" ).arg( i ) );
     942                 :            : #endif
     943                 :            : 
     944                 :            :       // Modify the first pass' image ...
     945                 :            :       {
     946                 :          0 :         QPainter tempPainter;
     947                 :            : 
     948                 :            :         // reuse the first pass painter, if available
     949                 :          0 :         QPainter *painter1 = job.firstPassJob->context.painter();
     950                 :          0 :         if ( ! painter1 )
     951                 :            :         {
     952                 :          0 :           tempPainter.begin( job.firstPassJob->img );
     953                 :          0 :           painter1 = &tempPainter;
     954                 :          0 :         }
     955                 :            : #if DEBUG_RENDERING
     956                 :            :         job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_1.png" ).arg( i ) );
     957                 :            : #endif
     958                 :            :         // ... first retain parts that are "outside" the mask image
     959                 :          0 :         painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
     960                 :          0 :         painter1->drawImage( 0, 0, *maskImage );
     961                 :            : 
     962                 :            : #if DEBUG_RENDERING
     963                 :            :         job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_2.png" ).arg( i ) );
     964                 :            : #endif
     965                 :            :         // ... and overpaint the second pass' image on it
     966                 :          0 :         painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
     967                 :          0 :         painter1->drawImage( 0, 0, *job.img );
     968                 :            : #if DEBUG_RENDERING
     969                 :            :         job.img->save( QString( "/tmp/second_%1_b.png" ).arg( i ) );
     970                 :            :         if ( job.firstPassJob )
     971                 :            :           job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_3.png" ).arg( i ) );
     972                 :            : #endif
     973                 :          0 :       }
     974                 :          0 :     }
     975                 :            :   }
     976                 :          0 : }
     977                 :            : 
     978                 :          0 : void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob )
     979                 :            : {
     980                 :          0 :   QgsSettings settings;
     981                 :          0 :   if ( !settings.value( QStringLiteral( "Map/logCanvasRefreshEvent" ), false ).toBool() )
     982                 :          0 :     return;
     983                 :            : 
     984                 :          0 :   QMultiMap<int, QString> elapsed;
     985                 :          0 :   const auto constJobs = jobs;
     986                 :          0 :   for ( const LayerRenderJob &job : constJobs )
     987                 :          0 :     elapsed.insert( job.renderingTime, job.layerId );
     988                 :          0 :   const auto constSecondPassJobs = secondPassJobs;
     989                 :          0 :   for ( const LayerRenderJob &job : constSecondPassJobs )
     990                 :          0 :     elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
     991                 :            : 
     992                 :          0 :   elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
     993                 :            : 
     994                 :          0 :   QList<int> tt( elapsed.uniqueKeys() );
     995                 :          0 :   std::sort( tt.begin(), tt.end(), std::greater<int>() );
     996                 :          0 :   const auto constTt = tt;
     997                 :          0 :   for ( int t : constTt )
     998                 :            :   {
     999                 :          0 :     QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
    1000                 :            :   }
    1001                 :          0 :   QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
    1002                 :          0 : }
    1003                 :            : 
    1004                 :          0 : void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
    1005                 :            : {
    1006                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
    1007                 :            : 
    1008                 :          0 :   QElapsedTimer t;
    1009                 :          0 :   t.start();
    1010                 :            : 
    1011                 :            :   // Reset the composition mode before rendering the labels
    1012                 :          0 :   painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
    1013                 :            : 
    1014                 :          0 :   renderContext.setPainter( painter );
    1015                 :            : 
    1016                 :          0 :   if ( labelingEngine2 )
    1017                 :            :   {
    1018                 :          0 :     labelingEngine2->run( renderContext );
    1019                 :          0 :   }
    1020                 :            : 
    1021                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
    1022                 :          0 : }
    1023                 :            : 
    1024                 :          0 : void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
    1025                 :            : {
    1026                 :          0 :   Q_UNUSED( settings )
    1027                 :            : 
    1028                 :          0 :   drawLabeling( renderContext, labelingEngine2, painter );
    1029                 :          0 : }
    1030                 :            : 
    1031                 :            : ///@endcond PRIVATE

Generated by: LCOV version 1.14