28#include <QApplication> 
   29#include <QCoreApplication> 
   31#include <QDomDocument> 
   38#include <QNetworkReply> 
   39#include <QNetworkRequest> 
   41#include <QImageReader> 
   42#include <QSvgRenderer> 
   43#include <QTemporaryDir> 
   48QgsImageCacheEntry::QgsImageCacheEntry( 
const QString &path, QSize size, 
const bool keepAspectRatio, 
const double opacity, 
double dpi, 
int frameNumber )
 
   51  , keepAspectRatio( keepAspectRatio )
 
   54  , frameNumber( frameNumber )
 
   60  const QgsImageCacheEntry *otherImage = 
dynamic_cast< const QgsImageCacheEntry * 
>( other );
 
   63       || otherImage->keepAspectRatio != keepAspectRatio
 
   64       || otherImage->frameNumber != frameNumber
 
   65       || otherImage->size != size
 
   66       || ( !size.isValid() && otherImage->targetDpi != targetDpi )
 
   67       || otherImage->opacity != opacity
 
   68       || otherImage->path != path )
 
   74int QgsImageCacheEntry::dataSize()
 const 
   77  if ( !image.isNull() )
 
   79    size += image.sizeInBytes();
 
   84void QgsImageCacheEntry::dump()
 const 
   86  QgsDebugMsgLevel( QStringLiteral( 
"path: %1, size %2x%3" ).arg( path ).arg( size.width() ).arg( size.height() ), 3 );
 
   94  mTemporaryDir.reset( 
new QTemporaryDir() );
 
   96  const int bytes = 
QgsSettings().
value( QStringLiteral( 
"/qgis/maxImageCacheSize" ), 0 ).toInt();
 
  106      if ( sysMemory >= 32000 ) 
 
  108      else if ( sysMemory >= 16000 ) 
 
  115  mMissingSvg = QStringLiteral( 
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
 
  118  if ( QFile::exists( downloadingSvgPath ) )
 
  120    QFile file( downloadingSvgPath );
 
  121    if ( file.open( QIODevice::ReadOnly ) )
 
  123      mFetchingSvg = file.readAll();
 
  127  if ( mFetchingSvg.isEmpty() )
 
  129    mFetchingSvg = QStringLiteral( 
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
 
  137QImage 
QgsImageCache::pathAsImage( 
const QString &f, 
const QSize size, 
const bool keepAspectRatio, 
const double opacity, 
bool &fitsInCache, 
bool blocking, 
double targetDpi, 
int frameNumber, 
bool *isMissing )
 
  140  int nextFrameDelayMs = 0;
 
  141  return pathAsImagePrivate( f, size, keepAspectRatio, opacity, fitsInCache, blocking, targetDpi, frameNumber, isMissing, 
totalFrameCount, nextFrameDelayMs );
 
  144QImage 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 )
 
  146  QString file = f.trimmed();
 
  150  if ( file.isEmpty() )
 
  153  const QMutexLocker locker( &
mMutex );
 
  155  const auto extractedAnimationIt = mExtractedAnimationPaths.constFind( file );
 
  156  if ( extractedAnimationIt != mExtractedAnimationPaths.constEnd() )
 
  158    file = QDir( extractedAnimationIt.value() ).filePath( QStringLiteral( 
"frame_%1.png" ).arg( frameNumber ) );
 
  164  QgsImageCacheEntry *currentEntry = 
findExistingEntry( 
new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi, frameNumber ) );
 
  171  if ( currentEntry->image.isNull() )
 
  173    long cachedDataSize = 0;
 
  174    bool isBroken = 
false;
 
  175    result = renderImage( file, size, keepAspectRatio, opacity, targetDpi, frameNumber, isBroken, 
totalFrameCount, nextFrameDelayMs, blocking );
 
  176    cachedDataSize += result.sizeInBytes();
 
  180      currentEntry->image = QImage();
 
  185      currentEntry->image = result;
 
  187      currentEntry->nextFrameDelay = nextFrameDelayMs;
 
  191      *isMissing = isBroken;
 
  192    currentEntry->isMissingImage = isBroken;
 
  198    result = currentEntry->image;
 
  200    nextFrameDelayMs = currentEntry->nextFrameDelay;
 
  202      *isMissing = currentEntry->isMissingImage;
 
  210  if ( path.isEmpty() )
 
  214  if ( !path.startsWith( QLatin1String( 
"base64:" ) ) && QFile::exists( path ) )
 
  216    const QImageReader reader( path );
 
  217    if ( reader.size().isValid() )
 
  218      return reader.size();
 
  220      return QImage( path ).size();
 
  224    QByteArray ba = 
getContent( path, QByteArray( 
"broken" ), QByteArray( 
"fetching" ), blocking );
 
  226    if ( ba != 
"broken" && ba != 
"fetching" )
 
  228      QBuffer buffer( &ba );
 
  229      buffer.open( QIODevice::ReadOnly );
 
  231      QImageReader reader( &buffer );
 
  234      const QSize s = reader.size();
 
  237      const QImage im = reader.read();
 
  238      return im.isNull() ? QSize() : im.size();
 
  246  const QString file = path.trimmed();
 
  248  if ( file.isEmpty() )
 
  251  const QMutexLocker locker( &
mMutex );
 
  253  auto it = mTotalFrameCounts.find( path );
 
  254  if ( it != mTotalFrameCounts.end() )
 
  258  int nextFrameDelayMs = 0;
 
  259  bool fitsInCache = 
false;
 
  260  bool isMissing = 
false;
 
  261  ( void )pathAsImagePrivate( file, QSize(), 
true, 1.0, fitsInCache, blocking, 96, 0, &isMissing, res, nextFrameDelayMs );
 
  268  const QString file = path.trimmed();
 
  270  if ( file.isEmpty() )
 
  273  const QMutexLocker locker( &
mMutex );
 
  275  auto it = mImageDelays.find( path );
 
  276  if ( it != mImageDelays.end() )
 
  277    return it.value().value( currentFrame ); 
 
  280  int nextFrameDelayMs = 0;
 
  281  bool fitsInCache = 
false;
 
  282  bool isMissing = 
false;
 
  283  const QImage res = pathAsImagePrivate( file, QSize(), 
true, 1.0, fitsInCache, blocking, 96, currentFrame, &isMissing, frameCount, nextFrameDelayMs );
 
  285  return nextFrameDelayMs <= 0 || res.isNull() ? -1 : nextFrameDelayMs;
 
  290  const QMutexLocker locker( &
mMutex );
 
  292  auto it = mExtractedAnimationPaths.find( path );
 
  293  if ( it != mExtractedAnimationPaths.end() )
 
  297  std::unique_ptr< QImageReader > reader;
 
  298  std::unique_ptr< QBuffer > buffer;
 
  300  if ( !path.startsWith( QLatin1String( 
"base64:" ) ) && QFile::exists( path ) )
 
  302    const QString basePart = QFileInfo( path ).baseName();
 
  304    filePath = mTemporaryDir->filePath( QStringLiteral( 
"%1_%2" ).arg( basePart ).arg( 
id ) );
 
  305    while ( QFile::exists( filePath ) )
 
  306      filePath = mTemporaryDir->filePath( QStringLiteral( 
"%1_%2" ).arg( basePart ).arg( ++
id ) );
 
  308    reader = std::make_unique< QImageReader >( path );
 
  312    QByteArray ba = 
getContent( path, QByteArray( 
"broken" ), QByteArray( 
"fetching" ), 
false );
 
  313    if ( ba == 
"broken" || ba == 
"fetching" )
 
  319      const QString path = QUuid::createUuid().toString( QUuid::WithoutBraces );
 
  320      filePath = mTemporaryDir->filePath( path );
 
  322      buffer = std::make_unique< QBuffer >( &ba );
 
  323      buffer->open( QIODevice::ReadOnly );
 
  324      reader = std::make_unique< QImageReader> ( buffer.get() );
 
  328  QDir().mkpath( filePath );
 
  329  mExtractedAnimationPaths.insert( path, filePath );
 
  331  const QDir frameDirectory( filePath );
 
  334  reader->setAutoTransform( 
true );
 
  338    const QImage frame = reader->read();
 
  339    if ( frame.isNull() )
 
  342    mImageDelays[ path ].append( reader->nextImageDelay() );
 
  344    const QString framePath = frameDirectory.filePath( QStringLiteral( 
"frame_%1.png" ).arg( frameNumber++ ) );
 
  345    frame.save( framePath, 
"PNG" );
 
  348  mTotalFrameCounts.insert( path, frameNumber );
 
  351QImage 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 
  357  if ( !path.startsWith( QLatin1String( 
"base64:" ) ) && QFile::exists( path ) )
 
  359    QImageReader reader( path );
 
  360    reader.setAutoTransform( 
true );
 
  362    if ( reader.format() == 
"pdf" )
 
  364      if ( !size.isEmpty() )
 
  371        reader.setScaledSize( size );
 
  376        const QSize sizeAt72Dpi = reader.size();
 
  377        const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
 
  378        reader.setScaledSize( sizeAtTargetDpi );
 
  384    if ( frameNumber == -1 )
 
  390      im = getFrameFromReader( reader, frameNumber );
 
  392    nextFrameDelayMs = reader.nextImageDelay();
 
  396    QByteArray ba = 
getContent( path, QByteArray( 
"broken" ), QByteArray( 
"fetching" ), blocking );
 
  398    if ( ba == 
"broken" )
 
  403      if ( !size.isValid() || size.isNull() )
 
  407      if ( size.width() == 0 )
 
  408        size.setWidth( size.height() );
 
  409      if ( size.height() == 0 )
 
  410        size.setHeight( size.width() );
 
  412      im = QImage( size, QImage::Format_ARGB32_Premultiplied );
 
  416      QSvgRenderer r( mMissingSvg );
 
  418      QSizeF s( r.viewBox().size() );
 
  419      s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
 
  420      const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
 
  421      r.render( &p, rect );
 
  423    else if ( ba == 
"fetching" )
 
  426      if ( size.width() == 0 )
 
  427        size.setWidth( size.height() );
 
  428      if ( size.height() == 0 )
 
  429        size.setHeight( size.width() );
 
  432      im = QImage( size, QImage::Format_ARGB32_Premultiplied );
 
  436      QSvgRenderer r( mFetchingSvg );
 
  438      QSizeF s( r.viewBox().size() );
 
  439      s.scale( size.width(), size.height(), Qt::KeepAspectRatio );
 
  440      const QRectF rect( ( size.width() - s.width() ) / 2, ( size.height() - s.height() ) / 2, s.width(), s.height() );
 
  441      r.render( &p, rect );
 
  445      QBuffer buffer( &ba );
 
  446      buffer.open( QIODevice::ReadOnly );
 
  448      QImageReader reader( &buffer );
 
  449      reader.setAutoTransform( 
true );
 
  451      if ( reader.format() == 
"pdf" )
 
  453        if ( !size.isEmpty() )
 
  460          reader.setScaledSize( size );
 
  465          const QSize sizeAt72Dpi = reader.size();
 
  466          const QSize sizeAtTargetDpi = sizeAt72Dpi * targetDpi / 72;
 
  467          reader.setScaledSize( sizeAtTargetDpi );
 
  472      if ( frameNumber == -1 )
 
  478        im = getFrameFromReader( reader, frameNumber );
 
  480      nextFrameDelayMs = reader.nextImageDelay();
 
  484  if ( !im.hasAlphaChannel() )
 
  485    im = im.convertToFormat( QImage::Format_ARGB32 );
 
  491  if ( !size.isValid() || size.isNull() || im.size() == size )
 
  494  else if ( keepAspectRatio && size.height() == 0 )
 
  495    return im.scaledToWidth( size.width(), Qt::SmoothTransformation );
 
  497  else if ( keepAspectRatio && size.width() == 0 )
 
  498    return im.scaledToHeight( size.height(), Qt::SmoothTransformation );
 
  500    return im.scaled( size, keepAspectRatio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
 
  503QImage QgsImageCache::getFrameFromReader( QImageReader &reader, 
int frameNumber )
 
  505  if ( reader.jumpToImage( frameNumber ) )
 
  506    return reader.read();
 
  509  for ( 
int frame = 0; frame < frameNumber; ++frame )
 
  511    if ( reader.read().isNull() )
 
  514  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)