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 ¶ms = 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 ¶ms = 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 : :
|