19#include "moc_qgsimagecache.cpp"
30#include <QApplication>
31#include <QCoreApplication>
33#include <QDomDocument>
40#include <QNetworkReply>
41#include <QNetworkRequest>
43#include <QImageReader>
44#include <QSvgRenderer>
45#include <QTemporaryDir>
50QgsImageCacheEntry::QgsImageCacheEntry(
const QString &path, QSize size,
const bool keepAspectRatio,
const double opacity,
double dpi,
int frameNumber )
53 , keepAspectRatio( keepAspectRatio )
56 , frameNumber( frameNumber )
62 const QgsImageCacheEntry *otherImage =
dynamic_cast< const QgsImageCacheEntry *
>( other );
65 || otherImage->keepAspectRatio != keepAspectRatio
66 || otherImage->frameNumber != frameNumber
67 || otherImage->size != size
68 || ( !size.isValid() && otherImage->targetDpi != targetDpi )
69 || otherImage->opacity != opacity
70 || otherImage->path != path )
76int QgsImageCacheEntry::dataSize()
const
79 if ( !image.isNull() )
81 size += image.sizeInBytes();
86void QgsImageCacheEntry::dump()
const
88 QgsDebugMsgLevel( QStringLiteral(
"path: %1, size %2x%3" ).arg( path ).arg( size.width() ).arg( size.height() ), 3 );
96 mTemporaryDir.reset(
new QTemporaryDir() );
98 const int bytes =
QgsSettings().
value( QStringLiteral(
"/qgis/maxImageCacheSize" ), 0 ).toInt();
108 if ( sysMemory >= 32000 )
110 else if ( sysMemory >= 16000 )
117 mMissingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
120 if ( QFile::exists( downloadingSvgPath ) )
122 QFile file( downloadingSvgPath );
123 if ( file.open( QIODevice::ReadOnly ) )
125 mFetchingSvg = file.readAll();
129 if ( mFetchingSvg.isEmpty() )
131 mFetchingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
139QImage
QgsImageCache::pathAsImage(
const QString &f,
const QSize size,
const bool keepAspectRatio,
const double opacity,
bool &fitsInCache,
bool blocking,
double targetDpi,
int frameNumber,
bool *isMissing )
142 int nextFrameDelayMs = 0;
143 return pathAsImagePrivate( f, size, keepAspectRatio, opacity, fitsInCache, blocking, targetDpi, frameNumber, isMissing,
totalFrameCount, nextFrameDelayMs );
146QImage 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 )
148 QString file = f.trimmed();
152 if ( file.isEmpty() )
155 const QMutexLocker locker( &
mMutex );
157 const auto extractedAnimationIt = mExtractedAnimationPaths.constFind( file );
158 if ( extractedAnimationIt != mExtractedAnimationPaths.constEnd() )
160 file = QDir( extractedAnimationIt.value() ).filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber ) );
166 QString base64String;
168 if (
parseBase64DataUrl( file, &mimeType, &base64String ) && mimeType.startsWith( QLatin1String(
"image/" ) ) )
170 file = QStringLiteral(
"base64:%1" ).arg( base64String );
173 QgsImageCacheEntry *currentEntry =
findExistingEntry(
new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi, frameNumber ) );
180 if ( currentEntry->image.isNull() )
182 long cachedDataSize = 0;
183 bool isBroken =
false;
184 result = renderImage( file, size, keepAspectRatio, opacity, targetDpi, frameNumber, isBroken,
totalFrameCount, nextFrameDelayMs, blocking );
185 cachedDataSize += result.sizeInBytes();
189 currentEntry->image = QImage();
194 currentEntry->image = result;
196 currentEntry->nextFrameDelay = nextFrameDelayMs;
200 *isMissing = isBroken;
201 currentEntry->isMissingImage = isBroken;
207 result = currentEntry->image;
209 nextFrameDelayMs = currentEntry->nextFrameDelay;
211 *isMissing = currentEntry->isMissingImage;
219 if ( path.isEmpty() )
225 const QImageReader reader( path );
226 if ( reader.size().isValid() )
227 return reader.size();
229 return QImage( path ).size();
233 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
235 if ( ba !=
"broken" && ba !=
"fetching" )
237 QBuffer buffer( &ba );
238 buffer.open( QIODevice::ReadOnly );
240 QImageReader reader( &buffer );
243 const QSize s = reader.size();
246 const QImage im = reader.read();
247 return im.isNull() ? QSize() : im.size();
255 const QString file = path.trimmed();
257 if ( file.isEmpty() )
260 const QMutexLocker locker( &
mMutex );
262 auto it = mTotalFrameCounts.find( path );
263 if ( it != mTotalFrameCounts.end() )
267 int nextFrameDelayMs = 0;
268 bool fitsInCache =
false;
269 bool isMissing =
false;
270 ( void )pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, 0, &isMissing, res, nextFrameDelayMs );
277 const QString file = path.trimmed();
279 if ( file.isEmpty() )
282 const QMutexLocker locker( &
mMutex );
284 auto it = mImageDelays.find( path );
285 if ( it != mImageDelays.end() )
286 return it.value().value( currentFrame );
289 int nextFrameDelayMs = 0;
290 bool fitsInCache =
false;
291 bool isMissing =
false;
292 const QImage res = pathAsImagePrivate( file, QSize(),
true, 1.0, fitsInCache, blocking, 96, currentFrame, &isMissing, frameCount, nextFrameDelayMs );
294 return nextFrameDelayMs <= 0 || res.isNull() ? -1 : nextFrameDelayMs;
299 const QMutexLocker locker( &
mMutex );
301 auto it = mExtractedAnimationPaths.find( path );
302 if ( it != mExtractedAnimationPaths.end() )
306 std::unique_ptr< QImageReader > reader;
307 std::unique_ptr< QBuffer > buffer;
311 const QString basePart = QFileInfo( path ).baseName();
313 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg(
id ) );
314 while ( QFile::exists( filePath ) )
315 filePath = mTemporaryDir->filePath( QStringLiteral(
"%1_%2" ).arg( basePart ).arg( ++
id ) );
317 reader = std::make_unique< QImageReader >( path );
321 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ),
false );
322 if ( ba ==
"broken" || ba ==
"fetching" )
328 const QString path = QUuid::createUuid().toString( QUuid::WithoutBraces );
329 filePath = mTemporaryDir->filePath( path );
331 buffer = std::make_unique< QBuffer >( &ba );
332 buffer->open( QIODevice::ReadOnly );
333 reader = std::make_unique< QImageReader> ( buffer.get() );
337 QDir().mkpath( filePath );
338 mExtractedAnimationPaths.insert( path, filePath );
340 const QDir frameDirectory( filePath );
343 reader->setAutoTransform(
true );
347 const QImage frame = reader->read();
348 if ( frame.isNull() )
351 mImageDelays[ path ].append( reader->nextImageDelay() );
353 const QString framePath = frameDirectory.filePath( QStringLiteral(
"frame_%1.png" ).arg( frameNumber++ ) );
354 frame.save( framePath,
"PNG" );
357 mTotalFrameCounts.insert( path, frameNumber );
360QImage 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
368 QImageReader reader( path );
369 reader.setAutoTransform(
true );
371 if ( reader.format() ==
"pdf" )
373 if ( !size.isEmpty() )
380 reader.setScaledSize( size );
385 const QSize sizeAt72Dpi = reader.size();
386 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
387 reader.setScaledSize( sizeAtTargetDpi );
393 if ( frameNumber == -1 )
399 im = getFrameFromReader( reader, frameNumber );
401 nextFrameDelayMs = reader.nextImageDelay();
405 QByteArray ba =
getContent( path, QByteArray(
"broken" ), QByteArray(
"fetching" ), blocking );
407 if ( ba ==
"broken" )
412 if ( !size.isValid() || size.isNull() )
416 if ( size.width() == 0 )
417 size.setWidth( size.height() );
418 if ( size.height() == 0 )
419 size.setHeight( size.width() );
421 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
425 QSvgRenderer r( mMissingSvg );
427 QSizeF s( r.viewBox().size() );
428 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
429 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
430 r.render( &p, rect );
432 else if ( ba ==
"fetching" )
435 if ( size.width() == 0 )
436 size.setWidth( size.height() );
437 if ( size.height() == 0 )
438 size.setHeight( size.width() );
441 im = QImage( size, QImage::Format_ARGB32_Premultiplied );
445 QSvgRenderer r( mFetchingSvg );
447 QSizeF s( r.viewBox().size() );
448 s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
449 const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
450 r.render( &p, rect );
454 QBuffer buffer( &ba );
455 buffer.open( QIODevice::ReadOnly );
457 QImageReader reader( &buffer );
458 reader.setAutoTransform(
true );
460 if ( reader.format() ==
"pdf" )
462 if ( !size.isEmpty() )
469 reader.setScaledSize( size );
474 const QSize sizeAt72Dpi = reader.size();
475 const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
476 reader.setScaledSize( sizeAtTargetDpi );
481 if ( frameNumber == -1 )
487 im = getFrameFromReader( reader, frameNumber );
489 nextFrameDelayMs = reader.nextImageDelay();
493 if ( !im.hasAlphaChannel()
494#
if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
495 && im.format() != QImage::Format_CMYK8888
498 im = im.convertToFormat( QImage::Format_ARGB32 );
504 if ( !size.isValid() || size.isNull() || im.size() == size )
507 else if ( keepAspectRatio && size.height() == 0 )
508 return im.scaledToWidth( size.width(), Qt::SmoothTransformation );
510 else if ( keepAspectRatio && size.width() == 0 )
511 return im.scaledToHeight( size.height(), Qt::SmoothTransformation );
513 return im.scaled( size, keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
516QImage QgsImageCache::getFrameFromReader( QImageReader &reader,
int frameNumber )
518 if ( reader.jumpToImage( frameNumber ) )
519 return reader.read();
522 for (
int frame = 0; frame < frameNumber; ++frame )
524 if ( reader.read().isNull() )
527 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)