29#include <QApplication>
30#include <QCoreApplication>
32#include <QDomDocument>
39#include <QNetworkReply>
40#include <QNetworkRequest>
42#include <QImageReader>
43#include <QSvgRenderer>
44#include <QTemporaryDir>
49QgsImageCacheEntry::QgsImageCacheEntry(
const QString &path, QSize size,
const bool keepAspectRatio,
const double opacity,
double dpi,
int frameNumber )
52 , keepAspectRatio( keepAspectRatio )
55 , frameNumber( frameNumber )
61 const QgsImageCacheEntry *otherImage =
dynamic_cast< const QgsImageCacheEntry *
>( other );
64 || otherImage->keepAspectRatio != keepAspectRatio
65 || otherImage->frameNumber != frameNumber
66 || otherImage->size != size
67 || ( !size.isValid() && otherImage->targetDpi != targetDpi )
68 || otherImage->opacity != opacity
69 || otherImage->path != path )
75int QgsImageCacheEntry::dataSize()
const
78 if ( !image.isNull() )
80 size += image.sizeInBytes();
85void QgsImageCacheEntry::dump()
const
87 QgsDebugMsgLevel( QStringLiteral(
"path: %1, size %2x%3" ).arg( path ).arg( size.width() ).arg( size.height() ), 3 );
95 mTemporaryDir.reset(
new QTemporaryDir() );
97 const int bytes =
QgsSettings().
value( QStringLiteral(
"/qgis/maxImageCacheSize" ), 0 ).toInt();
107 if ( sysMemory >= 32000 )
109 else if ( sysMemory >= 16000 )
116 mMissingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
119 if ( QFile::exists( downloadingSvgPath ) )
121 QFile file( downloadingSvgPath );
122 if ( file.open( QIODevice::ReadOnly ) )
124 mFetchingSvg = file.readAll();
128 if ( mFetchingSvg.isEmpty() )
130 mFetchingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
138QImage
QgsImageCache::pathAsImage(
const QString &f,
const QSize size,
const bool keepAspectRatio,
const double opacity,
bool &fitsInCache,
bool blocking,
double targetDpi,
int frameNumber,
bool *isMissing )
141 int nextFrameDelayMs = 0;
142 return pathAsImagePrivate( f, size, keepAspectRatio, opacity, fitsInCache, blocking, targetDpi, frameNumber, isMissing,
totalFrameCount, nextFrameDelayMs );
138QImage
QgsImageCache::pathAsImage(
const QString &f,
const QSize size,
const bool keepAspectRatio,
const double opacity,
bool &fitsInCache,
bool blocking,
double targetDpi,
int frameNumber,
bool *isMissing ) {
…}
145QImage QgsImageCache::pathAsImagePrivate(
const QString &f,
const QSize size,
const bool keepAspectRatio,
const double opacity,
bool &fitsInCache,
bool blocking,
double targetDpi,
int frameNumber,
bool *isMissing,
int &totalFrameCount,
int &nextFrameDelayMs )
147 QString file = f.trimmed();
151 if ( file.isEmpty() )
154 const QMutexLocker locker( &
mMutex );
156 const auto extractedAnimationIt = mExtractedAnimationPaths.constFind( file );
157 if ( extractedAnimationIt != mExtractedAnimationPaths.constEnd() )
159 file = QDir( extractedAnimationIt.value() ).filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber ) );
165 QgsImageCacheEntry *currentEntry =
findExistingEntry(
new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi, frameNumber ) );
172 if ( currentEntry->image.isNull() )
174 long cachedDataSize = 0;
175 bool isBroken =
false;
176 result = renderImage( file, size, keepAspectRatio, opacity, targetDpi, frameNumber, isBroken,
totalFrameCount, nextFrameDelayMs, blocking );
177 cachedDataSize += result.sizeInBytes();
181 currentEntry->image = QImage();
186 currentEntry->image = result;
188 currentEntry->nextFrameDelay = nextFrameDelayMs;
192 *isMissing = isBroken;
193 currentEntry->isMissingImage = isBroken;
199 result = currentEntry->image;
201 nextFrameDelayMs = currentEntry->nextFrameDelay;
203 *isMissing = currentEntry->isMissingImage;
211 if ( path.isEmpty() )
215 if ( !path.startsWith( QLatin1String(
"base64:" ) ) && QFile::exists( path ) )
217 const QImageReader reader( path );
218 if ( reader.size().isValid() )
219 return reader.size();
221 return QImage( path ).size();
225 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
227 if ( ba !=
"broken" && ba !=
"fetching" )
229 QBuffer buffer( &ba );
230 buffer.open( QIODevice::ReadOnly );
232 QImageReader reader( &buffer );
235 const QSize s = reader.size();
238 const QImage im = reader.read();
239 return im.isNull() ? QSize() : im.size();
247 const QString file = path.trimmed();
249 if ( file.isEmpty() )
252 const QMutexLocker locker( &
mMutex );
254 auto it = mTotalFrameCounts.find( path );
255 if ( it != mTotalFrameCounts.end() )
259 int nextFrameDelayMs = 0;
260 bool fitsInCache =
false;
261 bool isMissing =
false;
262 ( void )pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, 0, &isMissing, res, nextFrameDelayMs );
269 const QString file = path.trimmed();
271 if ( file.isEmpty() )
274 const QMutexLocker locker( &
mMutex );
276 auto it = mImageDelays.find( path );
277 if ( it != mImageDelays.end() )
278 return it.value().value( currentFrame );
281 int nextFrameDelayMs = 0;
282 bool fitsInCache =
false;
283 bool isMissing =
false;
284 const QImage res = pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, currentFrame, &isMissing, frameCount, nextFrameDelayMs );
286 return nextFrameDelayMs <= 0 || res.isNull() ? -1 : nextFrameDelayMs;
291 const QMutexLocker locker( &
mMutex );
293 auto it = mExtractedAnimationPaths.find( path );
294 if ( it != mExtractedAnimationPaths.end() )
298 std::unique_ptr< QImageReader > reader;
299 std::unique_ptr< QBuffer > buffer;
301 if ( !path.startsWith( QLatin1String(
"base64:" ) ) && QFile::exists( path ) )
303 const QString basePart = QFileInfo( path ).baseName();
305 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg(
id ) );
306 while ( QFile::exists( filePath ) )
307 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg( ++
id ) );
309 reader = std::make_unique< QImageReader >( path );
313 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ),
false );
314 if ( ba ==
"broken" || ba ==
"fetching" )
320 const QString path = QUuid::createUuid().toString( QUuid::WithoutBraces );
321 filePath = mTemporaryDir->filePath( path );
323 buffer = std::make_unique< QBuffer >( &ba );
324 buffer->open( QIODevice::ReadOnly );
325 reader = std::make_unique< QImageReader> ( buffer.get() );
329 QDir().mkpath( filePath );
330 mExtractedAnimationPaths.insert( path, filePath );
332 const QDir frameDirectory( filePath );
335 reader->setAutoTransform(
true );
339 const QImage frame = reader->read();
340 if ( frame.isNull() )
343 mImageDelays[ path ].append( reader->nextImageDelay() );
345 const QString framePath = frameDirectory.filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber++ ) );
346 frame.save( framePath,
"PNG" );
349 mTotalFrameCounts.insert( path, frameNumber );
352QImage QgsImageCache::renderImage(
const QString &path, QSize size,
const bool keepAspectRatio,
const double opacity,
double targetDpi,
int frameNumber,
bool &isBroken,
int &totalFrameCount,
int &nextFrameDelayMs,
bool blocking )
const
358 if ( !path.startsWith( QLatin1String(
"base64:" ) ) && QFile::exists( path ) )
360 QImageReader reader( path );
361 reader.setAutoTransform(
true );
363 if ( reader.format() ==
"pdf" )
365 if ( !size.isEmpty() )
372 reader.setScaledSize( size );
377 const QSize sizeAt72Dpi = reader.size();
378 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
379 reader.setScaledSize( sizeAtTargetDpi );
385 if ( frameNumber == -1 )
391 im = getFrameFromReader( reader, frameNumber );
393 nextFrameDelayMs = reader.nextImageDelay();
397 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
399 if ( ba ==
"broken" )
404 if ( !size.isValid() || size.isNull() )
408 if ( size.width() == 0 )
409 size.setWidth( size.height() );
410 if ( size.height() == 0 )
411 size.setHeight( size.width() );
413 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
417 QSvgRenderer r( mMissingSvg );
419 QSizeF s( r.viewBox().size() );
420 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
421 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
422 r.render( &p, rect );
424 else if ( ba ==
"fetching" )
427 if ( size.width() == 0 )
428 size.setWidth( size.height() );
429 if ( size.height() == 0 )
430 size.setHeight( size.width() );
433 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
437 QSvgRenderer r( mFetchingSvg );
439 QSizeF s( r.viewBox().size() );
440 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
441 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
442 r.render( &p, rect );
446 QBuffer buffer( &ba );
447 buffer.open( QIODevice::ReadOnly );
449 QImageReader reader( &buffer );
450 reader.setAutoTransform(
true );
452 if ( reader.format() ==
"pdf" )
454 if ( !size.isEmpty() )
461 reader.setScaledSize( size );
466 const QSize sizeAt72Dpi = reader.size();
467 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
468 reader.setScaledSize( sizeAtTargetDpi );
473 if ( frameNumber == -1 )
479 im = getFrameFromReader( reader, frameNumber );
481 nextFrameDelayMs = reader.nextImageDelay();
485 if ( !im.hasAlphaChannel()
486#
if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
487 && im.format() != QImage::Format_CMYK8888
490 im = im.convertToFormat( QImage::Format_ARGB32 );
496 if ( !size.isValid() || size.isNull() || im.size() == size )
499 else if ( keepAspectRatio && size.height() == 0 )
500 return im.scaledToWidth( size.width(), Qt::SmoothTransformation );
502 else if ( keepAspectRatio && size.width() == 0 )
503 return im.scaledToHeight( size.height(), Qt::SmoothTransformation );
505 return im.scaled( size, keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
508QImage QgsImageCache::getFrameFromReader( QImageReader &reader,
int frameNumber )
510 if ( reader.jumpToImage( frameNumber ) )
511 return reader.read();
514 for (
int frame = 0; frame < frameNumber; ++frame )
516 if ( reader.read().isNull() )
519 return reader.read();
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
Base class for entries in a QgsAbstractContentCache.
Abstract base class for file content caches, such as SVG or raster image caches.
long mMaxCacheSize
Maximum cache size.
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
QgsImageCacheEntry * findExistingEntry(QgsImageCacheEntry *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
long mTotalSize
Estimated total size of all cached content.
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit.
static QString defaultThemePath()
Returns the path to the default theme directory.
static int systemMemorySizeMb()
Returns the size of the system memory (RAM) in megabytes.
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
int nextFrameDelay(const QString &path, int currentFrame=0, bool blocking=false)
For image formats that support animation, this function returns the number of milliseconds to wait un...
QgsImageCache(QObject *parent=nullptr)
Constructor for QgsImageCache, with the specified parent object.
int totalFrameCount(const QString &path, bool blocking=false)
Returns the total frame count of the image at the specified path.
~QgsImageCache() override
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
void prepareAnimation(const QString &path)
Prepares for optimized retrieval of frames for the animation at the given path.
static void multiplyOpacity(QImage &image, double factor, QgsFeedback *feedback=nullptr)
Multiplies opacity of image pixel values by a factor.
This class is a composition of two QSettings instances:
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsgLevel(str, level)