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 );
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 QString base64String;
167 if (
parseBase64DataUrl( file, &mimeType, &base64String ) && mimeType.startsWith( QLatin1String(
"image/" ) ) )
169 file = QStringLiteral(
"base64:%1" ).arg( base64String );
172 QgsImageCacheEntry *currentEntry =
findExistingEntry(
new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi, frameNumber ) );
179 if ( currentEntry->image.isNull() )
181 long cachedDataSize = 0;
182 bool isBroken =
false;
183 result = renderImage( file, size, keepAspectRatio, opacity, targetDpi, frameNumber, isBroken,
totalFrameCount, nextFrameDelayMs, blocking );
184 cachedDataSize += result.sizeInBytes();
188 currentEntry->image = QImage();
193 currentEntry->image = result;
195 currentEntry->nextFrameDelay = nextFrameDelayMs;
199 *isMissing = isBroken;
200 currentEntry->isMissingImage = isBroken;
206 result = currentEntry->image;
208 nextFrameDelayMs = currentEntry->nextFrameDelay;
210 *isMissing = currentEntry->isMissingImage;
218 if ( path.isEmpty() )
224 const QImageReader reader( path );
225 if ( reader.size().isValid() )
226 return reader.size();
228 return QImage( path ).size();
232 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
234 if ( ba !=
"broken" && ba !=
"fetching" )
236 QBuffer buffer( &ba );
237 buffer.open( QIODevice::ReadOnly );
239 QImageReader reader( &buffer );
242 const QSize s = reader.size();
245 const QImage im = reader.read();
246 return im.isNull() ? QSize() : im.size();
254 const QString file = path.trimmed();
256 if ( file.isEmpty() )
259 const QMutexLocker locker( &
mMutex );
261 auto it = mTotalFrameCounts.find( path );
262 if ( it != mTotalFrameCounts.end() )
266 int nextFrameDelayMs = 0;
267 bool fitsInCache =
false;
268 bool isMissing =
false;
269 ( void )pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, 0, &isMissing, res, nextFrameDelayMs );
276 const QString file = path.trimmed();
278 if ( file.isEmpty() )
281 const QMutexLocker locker( &
mMutex );
283 auto it = mImageDelays.find( path );
284 if ( it != mImageDelays.end() )
285 return it.value().value( currentFrame );
288 int nextFrameDelayMs = 0;
289 bool fitsInCache =
false;
290 bool isMissing =
false;
291 const QImage res = pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, currentFrame, &isMissing, frameCount, nextFrameDelayMs );
293 return nextFrameDelayMs <= 0 || res.isNull() ? -1 : nextFrameDelayMs;
298 const QMutexLocker locker( &
mMutex );
300 auto it = mExtractedAnimationPaths.find( path );
301 if ( it != mExtractedAnimationPaths.end() )
305 std::unique_ptr< QImageReader > reader;
306 std::unique_ptr< QBuffer > buffer;
310 const QString basePart = QFileInfo( path ).baseName();
312 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg(
id ) );
313 while ( QFile::exists( filePath ) )
314 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg( ++
id ) );
316 reader = std::make_unique< QImageReader >( path );
320 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ),
false );
321 if ( ba ==
"broken" || ba ==
"fetching" )
327 const QString path = QUuid::createUuid().toString( QUuid::WithoutBraces );
328 filePath = mTemporaryDir->filePath( path );
330 buffer = std::make_unique< QBuffer >( &ba );
331 buffer->open( QIODevice::ReadOnly );
332 reader = std::make_unique< QImageReader> ( buffer.get() );
336 QDir().mkpath( filePath );
337 mExtractedAnimationPaths.insert( path, filePath );
339 const QDir frameDirectory( filePath );
342 reader->setAutoTransform(
true );
346 const QImage frame = reader->read();
347 if ( frame.isNull() )
350 mImageDelays[ path ].append( reader->nextImageDelay() );
352 const QString framePath = frameDirectory.filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber++ ) );
353 frame.save( framePath,
"PNG" );
356 mTotalFrameCounts.insert( path, frameNumber );
359QImage 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
367 QImageReader reader( path );
368 reader.setAutoTransform(
true );
370 if ( reader.format() ==
"pdf" )
372 if ( !size.isEmpty() )
379 reader.setScaledSize( size );
384 const QSize sizeAt72Dpi = reader.size();
385 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
386 reader.setScaledSize( sizeAtTargetDpi );
392 if ( frameNumber == -1 )
398 im = getFrameFromReader( reader, frameNumber );
400 nextFrameDelayMs = reader.nextImageDelay();
404 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
406 if ( ba ==
"broken" )
411 if ( !size.isValid() || size.isNull() )
415 if ( size.width() == 0 )
416 size.setWidth( size.height() );
417 if ( size.height() == 0 )
418 size.setHeight( size.width() );
420 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
424 QSvgRenderer r( mMissingSvg );
426 QSizeF s( r.viewBox().size() );
427 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
428 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
429 r.render( &p, rect );
431 else if ( ba ==
"fetching" )
434 if ( size.width() == 0 )
435 size.setWidth( size.height() );
436 if ( size.height() == 0 )
437 size.setHeight( size.width() );
440 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
444 QSvgRenderer r( mFetchingSvg );
446 QSizeF s( r.viewBox().size() );
447 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
448 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
449 r.render( &p, rect );
453 QBuffer buffer( &ba );
454 buffer.open( QIODevice::ReadOnly );
456 QImageReader reader( &buffer );
457 reader.setAutoTransform(
true );
459 if ( reader.format() ==
"pdf" )
461 if ( !size.isEmpty() )
468 reader.setScaledSize( size );
473 const QSize sizeAt72Dpi = reader.size();
474 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
475 reader.setScaledSize( sizeAtTargetDpi );
480 if ( frameNumber == -1 )
486 im = getFrameFromReader( reader, frameNumber );
488 nextFrameDelayMs = reader.nextImageDelay();
492 if ( !im.hasAlphaChannel()
493#
if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
494 && im.format() != QImage::Format_CMYK8888
497 im = im.convertToFormat( QImage::Format_ARGB32 );
503 if ( !size.isValid() || size.isNull() || im.size() == size )
506 else if ( keepAspectRatio && size.height() == 0 )
507 return im.scaledToWidth( size.width(), Qt::SmoothTransformation );
509 else if ( keepAspectRatio && size.width() == 0 )
510 return im.scaledToHeight( size.height(), Qt::SmoothTransformation );
512 return im.scaled( size, keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
515QImage QgsImageCache::getFrameFromReader( QImageReader &reader,
int frameNumber )
517 if ( reader.jumpToImage( frameNumber ) )
518 return reader.read();
521 for (
int frame = 0; frame < frameNumber; ++frame )
523 if ( reader.read().isNull() )
526 return reader.read();
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
static bool parseBase64DataUrl(const QString &path, QString *mimeType=nullptr, QString *data=nullptr)
Parses a path to determine if it represents a base 64 encoded HTML data URL, and if so,...
static bool isBase64Data(const QString &path)
Returns true if path represents base64 encoded data.
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)