Skip to content

Commit

Permalink
Merge pull request #60604 from nyalldawson/fix_51273
Browse files Browse the repository at this point in the history
Fix slow performance of raster image marker
  • Loading branch information
alexbruy authored Feb 14, 2025
2 parents 6525542 + 12cffa3 commit df80fe3
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 2 deletions.
84 changes: 84 additions & 0 deletions src/core/qgsimagecache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ QImage QgsImageCache::pathAsImagePrivate( const QString &f, const QSize size, co
}

QSize QgsImageCache::originalSize( const QString &path, bool blocking ) const
{
return mImageSizeCache.originalSize( path, blocking );
}

QSize QgsImageCache::originalSizePrivate( const QString &path, bool blocking ) const
{
if ( path.isEmpty() )
return QSize();
Expand Down Expand Up @@ -527,4 +532,83 @@ QImage QgsImageCache::getFrameFromReader( QImageReader &reader, int frameNumber
return reader.read();
}

///@cond PRIVATE
template class QgsAbstractContentCache<QgsImageCacheEntry>; // clazy:exclude=missing-qobject-macro

QgsImageSizeCacheEntry::QgsImageSizeCacheEntry( const QString &path )
: QgsAbstractContentCacheEntry( path )
{

}

int QgsImageSizeCacheEntry::dataSize() const
{
return sizeof( QSize );
}

void QgsImageSizeCacheEntry::dump() const
{
QgsDebugMsgLevel( QStringLiteral( "path: %1" ).arg( path ), 3 );
}

bool QgsImageSizeCacheEntry::isEqual( const QgsAbstractContentCacheEntry *other ) const
{
const QgsImageSizeCacheEntry *otherImage = dynamic_cast< const QgsImageSizeCacheEntry * >( other );
if ( !otherImage
|| otherImage->path != path )
return false;

return true;
}

template class QgsAbstractContentCache<QgsImageSizeCacheEntry>; // clazy:exclude=missing-qobject-macro


//
// QgsImageSizeCache
//

QgsImageSizeCache::QgsImageSizeCache( QObject *parent )
: QgsAbstractContentCache< QgsImageSizeCacheEntry >( parent, QObject::tr( "Image" ) )
{
mMaxCacheSize = 524288; // 500kb max cache size, we are only storing QSize objects here, so that should be heaps
}

QgsImageSizeCache::~QgsImageSizeCache() = default;

QSize QgsImageSizeCache::originalSize( const QString &f, bool blocking )
{
QString file = f.trimmed();

if ( file.isEmpty() )
return QSize();

const QMutexLocker locker( &mMutex );

QString base64String;
QString mimeType;
if ( parseBase64DataUrl( file, &mimeType, &base64String ) && mimeType.startsWith( QLatin1String( "image/" ) ) )
{
file = QStringLiteral( "base64:%1" ).arg( base64String );
}

QgsImageSizeCacheEntry *currentEntry = findExistingEntry( new QgsImageSizeCacheEntry( file ) );

QSize result;

if ( !currentEntry->size.isValid() )
{
result = QgsApplication::imageCache()->originalSizePrivate( file, blocking );
mTotalSize += currentEntry->dataSize();
currentEntry->size = result;
trimToMaximumSize();
}
else
{
result = currentEntry->size;
}

return result;
}

///@endcond
53 changes: 53 additions & 0 deletions src/core/qgsimagecache.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,54 @@ class QTemporaryDir;

///@cond PRIVATE

/**
* \ingroup core
* \class QgsImageSizeCacheEntry
* \brief An entry for a QgsImageSizeCache, representing the original size of a single raster image
* \since QGIS 3.42
*/
class CORE_EXPORT QgsImageSizeCacheEntry : public QgsAbstractContentCacheEntry
{
public:

/**
* Constructor for QgsImageSizeCacheEntry, corresponding to the specified image \a path.
*/
QgsImageSizeCacheEntry( const QString &path ) ;

//! Original image size
QSize size;

int dataSize() const override;
void dump() const override;
bool isEqual( const QgsAbstractContentCacheEntry *other ) const override;

};

/**
* \class QgsImageSizeCache
* \ingroup core
* \brief A cache for original image sizes.
*
* QgsImageSizeCache stores the original sizes of raster image files, allowing efficient
* reuse without incurring the cost of re-opening and parsing the image on every render.
*
* This is a private class, used internally in QgsImageCache.
*
* \since QGIS 3.42
*/
class CORE_EXPORT QgsImageSizeCache : public QgsAbstractContentCache< QgsImageSizeCacheEntry >
{
Q_OBJECT

public:

QgsImageSizeCache( QObject *parent SIP_TRANSFERTHIS = nullptr );
~QgsImageSizeCache() override;
long maximumSize() const { return mMaxCacheSize; }
QSize originalSize( const QString &path, bool blocking = false );
};

/**
* \ingroup core
* \class QgsImageCacheEntry
Expand Down Expand Up @@ -256,6 +304,8 @@ class CORE_EXPORT QgsImageCache : public QgsAbstractContentCache< QgsImageCacheE

static QImage getFrameFromReader( QImageReader &reader, int frameNumber );

QSize originalSizePrivate( const QString &path, bool blocking = false ) const;

//! SVG content to be rendered if SVG file was not found.
QByteArray mMissingSvg;

Expand All @@ -266,6 +316,9 @@ class CORE_EXPORT QgsImageCache : public QgsAbstractContentCache< QgsImageCacheE
QMap< QString, int > mTotalFrameCounts;
QMap< QString, QVector< int > > mImageDelays;

mutable QgsImageSizeCache mImageSizeCache;

friend class QgsImageSizeCache;
friend class TestQgsImageCache;
};

Expand Down
9 changes: 7 additions & 2 deletions src/core/symbology/qgsmarkersymbollayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3131,7 +3131,7 @@ bool QgsRasterMarkerSymbolLayer::setPreservedAspectRatio( bool par )

double QgsRasterMarkerSymbolLayer::updateDefaultAspectRatio()
{
if ( mDefaultAspectRatio == 0.0 )
if ( mDefaultAspectRatio == 0.0 && !mPath.isEmpty() )
{
const QSize size = QgsApplication::imageCache()->originalSize( mPath );
mDefaultAspectRatio = ( !size.isNull() && size.isValid() && size.width() > 0 ) ? static_cast< double >( size.height() ) / static_cast< double >( size.width() ) : 0.0;
Expand Down Expand Up @@ -3380,7 +3380,12 @@ QVariantMap QgsRasterMarkerSymbolLayer::properties() const

QgsRasterMarkerSymbolLayer *QgsRasterMarkerSymbolLayer::clone() const
{
auto m = std::make_unique< QgsRasterMarkerSymbolLayer >( mPath, mSize, mAngle );
auto m = std::make_unique< QgsRasterMarkerSymbolLayer >();
m->mPath = mPath;
m->mDefaultAspectRatio = mDefaultAspectRatio;
m->mSize = mSize;
m->mAngle = mAngle;
// other members are copied by:
copyCommonProperties( m.get() );
return m.release();
}
Expand Down

0 comments on commit df80fe3

Please sign in to comment.