LCOV - code coverage report
Current view: top level - core - qgsmaprenderercache.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 176 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgsmaprenderercache.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 "qgsmaprenderercache.h"
      17                 :            : 
      18                 :            : #include "qgsmaplayer.h"
      19                 :            : #include "qgsmaplayerlistutils.h"
      20                 :            : #include "qgsapplication.h"
      21                 :            : 
      22                 :            : #include <QImage>
      23                 :            : #include <QPainter>
      24                 :            : #include <algorithm>
      25                 :            : 
      26                 :          0 : QgsMapRendererCache::QgsMapRendererCache()
      27                 :          0 : {
      28                 :          0 :   clear();
      29                 :          0 : }
      30                 :            : 
      31                 :          0 : void QgsMapRendererCache::clear()
      32                 :            : {
      33                 :          0 :   QMutexLocker lock( &mMutex );
      34                 :          0 :   clearInternal();
      35                 :          0 : }
      36                 :            : 
      37                 :          0 : void QgsMapRendererCache::clearInternal()
      38                 :            : {
      39                 :          0 :   mExtent.setMinimal();
      40                 :          0 :   mScale = 0;
      41                 :            : 
      42                 :            :   // make sure we are disconnected from all layers
      43                 :          0 :   for ( const QgsWeakMapLayerPointer &layer : std::as_const( mConnectedLayers ) )
      44                 :            :   {
      45                 :          0 :     if ( layer.data() )
      46                 :            :     {
      47                 :          0 :       disconnect( layer.data(), &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
      48                 :          0 :       disconnect( layer.data(), &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
      49                 :          0 :     }
      50                 :            :   }
      51                 :          0 :   mCachedImages.clear();
      52                 :          0 :   mConnectedLayers.clear();
      53                 :          0 : }
      54                 :            : 
      55                 :          0 : void QgsMapRendererCache::dropUnusedConnections()
      56                 :            : {
      57                 :          0 :   QSet< QgsWeakMapLayerPointer > stillDepends = dependentLayers();
      58                 :          0 :   const QSet< QgsWeakMapLayerPointer > disconnects = mConnectedLayers.subtract( stillDepends );
      59                 :          0 :   for ( const QgsWeakMapLayerPointer &layer : disconnects )
      60                 :            :   {
      61                 :          0 :     if ( layer.data() )
      62                 :            :     {
      63                 :          0 :       disconnect( layer.data(), &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
      64                 :          0 :       disconnect( layer.data(), &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
      65                 :          0 :     }
      66                 :            :   }
      67                 :            : 
      68                 :          0 :   mConnectedLayers = stillDepends;
      69                 :          0 : }
      70                 :            : 
      71                 :          0 : QSet<QgsWeakMapLayerPointer > QgsMapRendererCache::dependentLayers() const
      72                 :            : {
      73                 :          0 :   QSet< QgsWeakMapLayerPointer > result;
      74                 :          0 :   QMap<QString, CacheParameters>::const_iterator it = mCachedImages.constBegin();
      75                 :          0 :   for ( ; it != mCachedImages.constEnd(); ++it )
      76                 :            :   {
      77                 :          0 :     const auto dependentLayers { it.value().dependentLayers };
      78                 :          0 :     for ( const QgsWeakMapLayerPointer &l : dependentLayers )
      79                 :            :     {
      80                 :          0 :       if ( l.data() )
      81                 :          0 :         result << l;
      82                 :            :     }
      83                 :          0 :   }
      84                 :          0 :   return result;
      85                 :          0 : }
      86                 :            : 
      87                 :          0 : bool QgsMapRendererCache::init( const QgsRectangle &extent, double scale )
      88                 :            : {
      89                 :          0 :   QMutexLocker lock( &mMutex );
      90                 :            : 
      91                 :            :   // check whether the params are the same
      92                 :          0 :   if ( extent == mExtent &&
      93                 :          0 :        qgsDoubleNear( scale, mScale ) )
      94                 :          0 :     return true;
      95                 :            : 
      96                 :          0 :   clearInternal();
      97                 :            : 
      98                 :            :   // set new params
      99                 :          0 :   mExtent = extent;
     100                 :          0 :   mScale = scale;
     101                 :          0 :   mMtp = QgsMapToPixel::fromScale( scale, QgsUnitTypes::DistanceUnit::DistanceUnknownUnit );
     102                 :            : 
     103                 :          0 :   return false;
     104                 :          0 : }
     105                 :            : 
     106                 :          0 : bool QgsMapRendererCache::updateParameters( const QgsRectangle &extent, const QgsMapToPixel &mtp )
     107                 :            : {
     108                 :          0 :   QMutexLocker lock( &mMutex );
     109                 :            : 
     110                 :            :   // check whether the params are the same
     111                 :          0 :   if ( extent == mExtent &&
     112                 :          0 :        mtp.transform() == mMtp.transform() )
     113                 :          0 :     return true;
     114                 :            : 
     115                 :            :   // set new params
     116                 :            : 
     117                 :          0 :   mExtent = extent;
     118                 :          0 :   mScale = 1.0;
     119                 :          0 :   mMtp = mtp;
     120                 :            : 
     121                 :          0 :   return false;
     122                 :          0 : }
     123                 :            : 
     124                 :          0 : void QgsMapRendererCache::setCacheImage( const QString &cacheKey, const QImage &image, const QList<QgsMapLayer *> &dependentLayers )
     125                 :            : {
     126                 :          0 :   QMutexLocker lock( &mMutex );
     127                 :            : 
     128                 :          0 :   QgsRectangle extent = mExtent;
     129                 :          0 :   QgsMapToPixel mapToPixel = mMtp;
     130                 :            : 
     131                 :          0 :   lock.unlock();
     132                 :          0 :   setCacheImageWithParameters( cacheKey, image, extent, mapToPixel, dependentLayers );
     133                 :          0 : }
     134                 :            : 
     135                 :          0 : void QgsMapRendererCache::setCacheImageWithParameters( const QString &cacheKey, const QImage &image, const QgsRectangle &extent, const QgsMapToPixel &mapToPixel, const QList<QgsMapLayer *> &dependentLayers )
     136                 :            : {
     137                 :          0 :   QMutexLocker lock( &mMutex );
     138                 :            : 
     139                 :          0 :   if ( extent != mExtent || mapToPixel != mMtp )
     140                 :            :   {
     141                 :          0 :     auto it = mCachedImages.constFind( cacheKey );
     142                 :          0 :     if ( it != mCachedImages.constEnd() )
     143                 :            :     {
     144                 :            :       // if the specified extent or map to pixel differs from the current cache parameters, AND
     145                 :            :       // there's an existing cached image with parameters which DO match the current cache parameters,
     146                 :            :       // then we leave the existing image intact and discard the one with non-matching parameters
     147                 :          0 :       if ( it->cachedExtent == mExtent && it->cachedMtp == mMtp )
     148                 :          0 :         return;
     149                 :          0 :     }
     150                 :          0 :   }
     151                 :            : 
     152                 :          0 :   CacheParameters params;
     153                 :          0 :   params.cachedImage = image;
     154                 :          0 :   params.cachedExtent = extent;
     155                 :          0 :   params.cachedMtp = mapToPixel;
     156                 :            : 
     157                 :            :   // connect to the layer to listen to layer's repaintRequested() signals
     158                 :          0 :   for ( QgsMapLayer *layer : dependentLayers )
     159                 :            :   {
     160                 :          0 :     if ( layer )
     161                 :            :     {
     162                 :          0 :       params.dependentLayers << layer;
     163                 :          0 :       if ( !mConnectedLayers.contains( QgsWeakMapLayerPointer( layer ) ) )
     164                 :            :       {
     165                 :          0 :         connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapRendererCache::layerRequestedRepaint );
     166                 :          0 :         connect( layer, &QgsMapLayer::willBeDeleted, this, &QgsMapRendererCache::layerRequestedRepaint );
     167                 :          0 :         mConnectedLayers << layer;
     168                 :          0 :       }
     169                 :          0 :     }
     170                 :            :   }
     171                 :            : 
     172                 :          0 :   mCachedImages[cacheKey] = params;
     173                 :          0 : }
     174                 :            : 
     175                 :          0 : bool QgsMapRendererCache::hasCacheImage( const QString &cacheKey ) const
     176                 :            : {
     177                 :          0 :   QMutexLocker lock( &mMutex );
     178                 :            : 
     179                 :          0 :   auto it = mCachedImages.constFind( cacheKey );
     180                 :          0 :   if ( it != mCachedImages.constEnd() )
     181                 :            :   {
     182                 :          0 :     const CacheParameters &params = it.value();
     183                 :          0 :     return ( params.cachedExtent == mExtent &&
     184                 :          0 :              params.cachedMtp.transform() == mMtp.transform() );
     185                 :            :   }
     186                 :            :   else
     187                 :            :   {
     188                 :          0 :     return false;
     189                 :            :   }
     190                 :          0 : }
     191                 :            : 
     192                 :          0 : bool QgsMapRendererCache::hasAnyCacheImage( const QString &cacheKey, double minimumScaleThreshold, double maximumScaleThreshold ) const
     193                 :            : {
     194                 :          0 :   auto it = mCachedImages.constFind( cacheKey );
     195                 :          0 :   if ( it != mCachedImages.constEnd() )
     196                 :            :   {
     197                 :          0 :     const CacheParameters &params = it.value();
     198                 :            : 
     199                 :            :     // check if cached image is outside desired scale range
     200                 :          0 :     if ( minimumScaleThreshold != 0 && mMtp.mapUnitsPerPixel() < params.cachedMtp.mapUnitsPerPixel() * minimumScaleThreshold )
     201                 :          0 :       return false;
     202                 :          0 :     if ( maximumScaleThreshold != 0 && mMtp.mapUnitsPerPixel() > params.cachedMtp.mapUnitsPerPixel() * maximumScaleThreshold )
     203                 :          0 :       return false;
     204                 :            : 
     205                 :          0 :     return true;
     206                 :            :   }
     207                 :            :   else
     208                 :            :   {
     209                 :          0 :     return false;
     210                 :            :   }
     211                 :          0 : }
     212                 :            : 
     213                 :          0 : QImage QgsMapRendererCache::cacheImage( const QString &cacheKey ) const
     214                 :            : {
     215                 :          0 :   QMutexLocker lock( &mMutex );
     216                 :          0 :   return mCachedImages.value( cacheKey ).cachedImage;
     217                 :          0 : }
     218                 :            : 
     219                 :          0 : static QPointF _transform( const QgsMapToPixel &mtp, const QgsPointXY &point, double scale )
     220                 :            : {
     221                 :          0 :   qreal x = point.x(), y = point.y();
     222                 :          0 :   mtp.transformInPlace( x, y );
     223                 :          0 :   return QPointF( x, y ) * scale;
     224                 :            : }
     225                 :            : 
     226                 :          0 : QImage QgsMapRendererCache::transformedCacheImage( const QString &cacheKey, const QgsMapToPixel &mtp ) const
     227                 :            : {
     228                 :          0 :   QMutexLocker lock( &mMutex );
     229                 :          0 :   const CacheParameters params = mCachedImages.value( cacheKey );
     230                 :            : 
     231                 :          0 :   if ( params.cachedExtent == mExtent &&
     232                 :          0 :        mtp.transform() == mMtp.transform() )
     233                 :            :   {
     234                 :          0 :     return params.cachedImage;
     235                 :            :   }
     236                 :            :   else
     237                 :            :   {
     238                 :            :     // no not use cache when the canvas rotation just changed
     239                 :            :     // https://github.com/qgis/QGIS/issues/41360
     240                 :          0 :     if ( !qgsDoubleNear( mtp.mapRotation(), params.cachedMtp.mapRotation() ) )
     241                 :          0 :       return QImage();
     242                 :            : 
     243                 :          0 :     QgsRectangle intersection = mExtent.intersect( params.cachedExtent );
     244                 :          0 :     if ( intersection.isNull() )
     245                 :          0 :       return QImage();
     246                 :            : 
     247                 :            :     // Calculate target rect
     248                 :          0 :     const QPointF ulT = _transform( mtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ), 1.0 );
     249                 :          0 :     const QPointF lrT = _transform( mtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ), 1.0 );
     250                 :          0 :     const QRectF targetRect( ulT.x(), ulT.y(), lrT.x() - ulT.x(), lrT.y() - ulT.y() );
     251                 :            : 
     252                 :            :     // Calculate source rect
     253                 :          0 :     const QPointF ulS = _transform( params.cachedMtp, QgsPointXY( intersection.xMinimum(), intersection.yMaximum() ),  params.cachedImage.devicePixelRatio() );
     254                 :          0 :     const QPointF lrS = _transform( params.cachedMtp, QgsPointXY( intersection.xMaximum(), intersection.yMinimum() ),  params.cachedImage.devicePixelRatio() );
     255                 :          0 :     const QRectF sourceRect( ulS.x(), ulS.y(), lrS.x() - ulS.x(), lrS.y() - ulS.y() );
     256                 :            : 
     257                 :            :     // Draw image
     258                 :          0 :     QImage ret( params.cachedImage.size(), params.cachedImage.format() );
     259                 :          0 :     ret.setDevicePixelRatio( params.cachedImage.devicePixelRatio() );
     260                 :          0 :     ret.setDotsPerMeterX( params.cachedImage.dotsPerMeterX() );
     261                 :          0 :     ret.setDotsPerMeterY( params.cachedImage.dotsPerMeterY() );
     262                 :          0 :     ret.fill( Qt::transparent );
     263                 :          0 :     QPainter painter;
     264                 :          0 :     painter.begin( &ret );
     265                 :          0 :     painter.drawImage( targetRect, params.cachedImage, sourceRect );
     266                 :          0 :     painter.end();
     267                 :          0 :     return ret;
     268                 :          0 :   }
     269                 :          0 : }
     270                 :            : 
     271                 :          0 : QList< QgsMapLayer * > QgsMapRendererCache::dependentLayers( const QString &cacheKey ) const
     272                 :            : {
     273                 :          0 :   auto it = mCachedImages.constFind( cacheKey );
     274                 :          0 :   if ( it != mCachedImages.constEnd() )
     275                 :            :   {
     276                 :          0 :     return _qgis_listQPointerToRaw( ( *it ).dependentLayers );
     277                 :            :   }
     278                 :          0 :   return QList< QgsMapLayer * >();
     279                 :          0 : }
     280                 :            : 
     281                 :            : 
     282                 :          0 : void QgsMapRendererCache::layerRequestedRepaint()
     283                 :            : {
     284                 :          0 :   QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
     285                 :          0 :   invalidateCacheForLayer( layer );
     286                 :          0 : }
     287                 :            : 
     288                 :          0 : void QgsMapRendererCache::invalidateCacheForLayer( QgsMapLayer *layer )
     289                 :            : {
     290                 :          0 :   if ( !layer )
     291                 :          0 :     return;
     292                 :            : 
     293                 :          0 :   QMutexLocker lock( &mMutex );
     294                 :            : 
     295                 :            :   // check through all cached images to clear any which depend on this layer
     296                 :          0 :   QMap<QString, CacheParameters>::iterator it = mCachedImages.begin();
     297                 :          0 :   for ( ; it != mCachedImages.end(); )
     298                 :            :   {
     299                 :          0 :     if ( !it.value().dependentLayers.contains( layer ) )
     300                 :            :     {
     301                 :          0 :       ++it;
     302                 :          0 :       continue;
     303                 :            :     }
     304                 :            : 
     305                 :          0 :     it = mCachedImages.erase( it );
     306                 :            :   }
     307                 :          0 :   dropUnusedConnections();
     308                 :          0 : }
     309                 :            : 
     310                 :          0 : void QgsMapRendererCache::clearCacheImage( const QString &cacheKey )
     311                 :            : {
     312                 :          0 :   QMutexLocker lock( &mMutex );
     313                 :            : 
     314                 :          0 :   mCachedImages.remove( cacheKey );
     315                 :          0 :   dropUnusedConnections();
     316                 :          0 : }
     317                 :            : 

Generated by: LCOV version 1.14