QGIS API Documentation  3.14.0-Pi (9f7028fd23)
qgsimagecache.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsimagecache.cpp
3  -----------------
4  begin : December 2018
5  copyright : (C) 2018 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsimagecache.h"
19 
20 #include "qgis.h"
21 #include "qgsimageoperation.h"
22 #include "qgslogger.h"
24 #include "qgsmessagelog.h"
26 
27 #include <QApplication>
28 #include <QCoreApplication>
29 #include <QCursor>
30 #include <QDomDocument>
31 #include <QDomElement>
32 #include <QFile>
33 #include <QImage>
34 #include <QPainter>
35 #include <QPicture>
36 #include <QFileInfo>
37 #include <QNetworkReply>
38 #include <QNetworkRequest>
39 #include <QBuffer>
40 #include <QImageReader>
41 #include <QSvgRenderer>
42 
44 
45 QgsImageCacheEntry::QgsImageCacheEntry( const QString &path, QSize size, const bool keepAspectRatio, const double opacity )
47  , size( size )
48  , keepAspectRatio( keepAspectRatio )
49  , opacity( opacity )
50 {
51 }
52 
53 bool QgsImageCacheEntry::isEqual( const QgsAbstractContentCacheEntry *other ) const
54 {
55  const QgsImageCacheEntry *otherImage = dynamic_cast< const QgsImageCacheEntry * >( other );
56  // cheapest checks first!
57  if ( !otherImage || otherImage->keepAspectRatio != keepAspectRatio || otherImage->size != size || otherImage->path != path || otherImage->opacity != opacity )
58  return false;
59 
60  return true;
61 }
62 
63 int QgsImageCacheEntry::dataSize() const
64 {
65  int size = 0;
66  if ( !image.isNull() )
67  {
68  size += ( image.width() * image.height() * 32 );
69  }
70  return size;
71 }
72 
73 void QgsImageCacheEntry::dump() const
74 {
75  QgsDebugMsgLevel( QStringLiteral( "path: %1, size %2x%3" ).arg( path ).arg( size.width() ).arg( size.height() ), 3 );
76 }
77 
79 
80 
81 QgsImageCache::QgsImageCache( QObject *parent )
82  : QgsAbstractContentCache< QgsImageCacheEntry >( parent, QObject::tr( "Image" ) )
83 {
84  mMissingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
85 
86  const QString downloadingSvgPath = QgsApplication::defaultThemePath() + QStringLiteral( "downloading_svg.svg" );
87  if ( QFile::exists( downloadingSvgPath ) )
88  {
89  QFile file( downloadingSvgPath );
90  if ( file.open( QIODevice::ReadOnly ) )
91  {
92  mFetchingSvg = file.readAll();
93  }
94  }
95 
96  if ( mFetchingSvg.isEmpty() )
97  {
98  mFetchingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
99  }
100 
102 }
103 
104 QImage QgsImageCache::pathAsImage( const QString &f, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking, bool *isMissing )
105 {
106  const QString file = f.trimmed();
107  if ( isMissing )
108  *isMissing = true;
109 
110  if ( file.isEmpty() )
111  return QImage();
112 
113  QMutexLocker locker( &mMutex );
114 
115  fitsInCache = true;
116 
117  QgsImageCacheEntry *currentEntry = findExistingEntry( new QgsImageCacheEntry( file, size, keepAspectRatio, opacity ) );
118 
119  QImage result;
120 
121  //if current entry image is null: create the image
122  // checks to see if image will fit into cache
123  //update stats for memory usage
124  if ( currentEntry->image.isNull() )
125  {
126  long cachedDataSize = 0;
127  bool isBroken = false;
128  result = renderImage( file, size, keepAspectRatio, opacity, isBroken, blocking );
129  cachedDataSize += result.width() * result.height() * 32;
130  if ( cachedDataSize > mMaxCacheSize / 2 )
131  {
132  fitsInCache = false;
133  currentEntry->image = QImage();
134  }
135  else
136  {
137  mTotalSize += ( result.width() * result.height() * 32 );
138  currentEntry->image = result;
139  }
140 
141  if ( isMissing )
142  *isMissing = isBroken;
143  currentEntry->isMissingImage = isBroken;
144 
146  }
147  else
148  {
149  result = currentEntry->image;
150  if ( isMissing )
151  *isMissing = currentEntry->isMissingImage;
152  }
153 
154  return result;
155 }
156 
157 QSize QgsImageCache::originalSize( const QString &path, bool blocking ) const
158 {
159  if ( path.isEmpty() )
160  return QSize();
161 
162  // direct read if path is a file -- maybe more efficient than going the bytearray route? (untested!)
163  if ( !path.startsWith( QStringLiteral( "base64:" ) ) && QFile::exists( path ) )
164  {
165  QImageReader reader( path );
166  if ( reader.size().isValid() )
167  return reader.size();
168  else
169  return QImage( path ).size();
170  }
171  else
172  {
173  QByteArray ba = getContent( path, QByteArray( "broken" ), QByteArray( "fetching" ), blocking );
174 
175  if ( ba != "broken" && ba != "fetching" )
176  {
177  QBuffer buffer( &ba );
178  buffer.open( QIODevice::ReadOnly );
179 
180  QImageReader reader( &buffer );
181  // if QImageReader::size works, then it's more efficient as it doesn't
182  // read the whole image (see Qt docs)
183  const QSize s = reader.size();
184  if ( s.isValid() )
185  return s;
186  QImage im = reader.read();
187  return im.isNull() ? QSize() : im.size();
188  }
189  }
190  return QSize();
191 }
192 
193 QImage QgsImageCache::renderImage( const QString &path, QSize size, const bool keepAspectRatio, const double opacity, bool &isBroken, bool blocking ) const
194 {
195  QImage im;
196  isBroken = false;
197 
198  // direct read if path is a file -- maybe more efficient than going the bytearray route? (untested!)
199  if ( !path.startsWith( QStringLiteral( "base64:" ) ) && QFile::exists( path ) )
200  {
201  im = QImage( path );
202  }
203  else
204  {
205  QByteArray ba = getContent( path, QByteArray( "broken" ), QByteArray( "fetching" ), blocking );
206 
207  if ( ba == "broken" )
208  {
209  isBroken = true;
210 
211  // if the size parameter is not valid, skip drawing of missing image symbol
212  if ( !size.isValid() )
213  return im;
214 
215  // if image size is set to respect aspect ratio, correct for broken image aspect ratio
216  if ( size.width() == 0 )
217  size.setWidth( size.height() );
218  if ( size.height() == 0 )
219  size.setHeight( size.width() );
220  // render "broken" svg
221  im = QImage( size, QImage::Format_ARGB32_Premultiplied );
222  im.fill( 0 ); // transparent background
223 
224  QPainter p( &im );
225  QSvgRenderer r( mMissingSvg );
226 
227  QSizeF s( r.viewBox().size() );
228  s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
229  QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
230  r.render( &p, rect );
231  }
232  else if ( ba == "fetching" )
233  {
234  // if image size is set to respect aspect ratio, correct for broken image aspect ratio
235  if ( size.width() == 0 )
236  size.setWidth( size.height() );
237  if ( size.height() == 0 )
238  size.setHeight( size.width() );
239 
240  // render "fetching" svg
241  im = QImage( size, QImage::Format_ARGB32_Premultiplied );
242  im.fill( 0 ); // transparent background
243 
244  QPainter p( &im );
245  QSvgRenderer r( mFetchingSvg );
246 
247  QSizeF s( r.viewBox().size() );
248  s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
249  QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
250  r.render( &p, rect );
251  }
252  else
253  {
254  QBuffer buffer( &ba );
255  buffer.open( QIODevice::ReadOnly );
256 
257  QImageReader reader( &buffer );
258  im = reader.read();
259  }
260  }
261 
262  if ( !im.hasAlphaChannel() )
263  im = im.convertToFormat( QImage::Format_ARGB32 );
264 
265  if ( opacity < 1.0 )
266  QgsImageOperation::multiplyOpacity( im, opacity );
267 
268  // render image at desired size -- null size means original size
269  if ( !size.isValid() || size.isNull() || im.size() == size )
270  return im;
271  // when original aspect ratio is respected and provided height value is 0, automatically compute height
272  else if ( keepAspectRatio && size.height() == 0 )
273  return im.scaledToWidth( size.width(), Qt::SmoothTransformation );
274  // when original aspect ratio is respected and provided width value is 0, automatically compute width
275  else if ( keepAspectRatio && size.width() == 0 )
276  return im.scaledToHeight( size.height(), Qt::SmoothTransformation );
277  else
278  return im.scaled( size, keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
279 }
QgsImageCache::originalSize
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
Definition: qgsimagecache.cpp:157
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsImageOperation::multiplyOpacity
static void multiplyOpacity(QImage &image, double factor)
Multiplies opacity of image pixel values by a factor.
Definition: qgsimageoperation.cpp:322
qgis.h
qgsnetworkcontentfetchertask.h
qgsimageoperation.h
QgsImageCache::pathAsImage
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
Definition: qgsimagecache.cpp:104
qgsnetworkaccessmanager.h
QgsImageCache::QgsImageCache
QgsImageCache(QObject *parent=nullptr)
Constructor for QgsImageCache, with the specified parent object.
Definition: qgsimagecache.cpp:81
QgsAbstractContentCacheEntry
Definition: qgsabstractcontentcache.h:47
QgsAbstractContentCache< QgsImageCacheEntry >::findExistingEntry
QgsImageCacheEntry * findExistingEntry(QgsImageCacheEntry *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
Definition: qgsabstractcontentcache.h:492
qgsimagecache.h
QgsAbstractContentCacheBase::remoteContentFetched
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
QgsImageCache::remoteImageFetched
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
qgslogger.h
QgsAbstractContentCache< QgsImageCacheEntry >::trimToMaximumSize
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit.
Definition: qgsabstractcontentcache.h:227
QgsAbstractContentCache
Definition: qgsabstractcontentcache.h:189
QgsApplication::defaultThemePath
static QString defaultThemePath()
Returns the path to the default theme directory.
Definition: qgsapplication.cpp:565
QgsAbstractContentCache< QgsImageCacheEntry >::mMutex
QMutex mMutex
Definition: qgsabstractcontentcache.h:549
QgsAbstractContentCache< QgsImageCacheEntry >::mTotalSize
long mTotalSize
Estimated total size of all cached content.
Definition: qgsabstractcontentcache.h:551
qgsmessagelog.h
QgsAbstractContentCache< QgsImageCacheEntry >::getContent
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
Definition: qgsabstractcontentcache.h:260
QgsAbstractContentCache< QgsImageCacheEntry >::mMaxCacheSize
long mMaxCacheSize
Maximum cache size.
Definition: qgsabstractcontentcache.h:554