Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add a target dpi argument to QgsImageCache
This allows callers to specify the desired rendering resolution of the
image when they aren't explicitly requesting a preset image size.

It can be used for image drivers for formats which don't have a fixed
size (e.g. pdf) so that the image is rendered in the desired resolution.
  • Loading branch information
nyalldawson committed Sep 27, 2021
1 parent e98c916 commit 51cb63f
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 14 deletions.
7 changes: 6 additions & 1 deletion python/core/auto_generated/qgsimagecache.sip.in
Expand Up @@ -35,7 +35,7 @@ reuse without incurring the cost of resampling on every render.
Constructor for QgsImageCache, with the specified ``parent`` object.
%End

QImage pathAsImage( const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache /Out/, bool blocking = false );
QImage pathAsImage( const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache /Out/, bool blocking = false, double targetDpi = 96 );
%Docstring
Returns the specified ``path`` rendered as an image. If possible, a pre-existing cached
version of the image will be used. If not, the image is fetched and resampled to the desired
Expand All @@ -59,6 +59,11 @@ The ``blocking`` boolean forces to wait for loading before returning image. The
in the same thread to ensure provided the image. WARNING: the ``blocking`` parameter must NEVER
be ``True`` from GUI based applications (like the main QGIS application) or crashes will result. Only for
use in external scripts or QGIS server.

Since QGIS 3.22 the ``targetDpi`` argument can be used to specify an explict DPI to render the image
at. This is used for some image formats (e.g. PDF) to ensure that content is rendered at the desired
DPI. This argument is only used when an invalid ``size`` argument is specified. If a valid ``size`` is
specified then the image will always be rendered at this size, regardless of the ``targetDpi``.
%End

QSize originalSize( const QString &path, bool blocking = false ) const;
Expand Down
13 changes: 7 additions & 6 deletions src/core/qgsimagecache.cpp
Expand Up @@ -42,19 +42,20 @@

///@cond PRIVATE

QgsImageCacheEntry::QgsImageCacheEntry( const QString &path, QSize size, const bool keepAspectRatio, const double opacity )
QgsImageCacheEntry::QgsImageCacheEntry( const QString &path, QSize size, const bool keepAspectRatio, const double opacity, double dpi )
: QgsAbstractContentCacheEntry( path )
, size( size )
, keepAspectRatio( keepAspectRatio )
, opacity( opacity )
, targetDpi( dpi )
{
}

bool QgsImageCacheEntry::isEqual( const QgsAbstractContentCacheEntry *other ) const
{
const QgsImageCacheEntry *otherImage = dynamic_cast< const QgsImageCacheEntry * >( other );
// cheapest checks first!
if ( !otherImage || otherImage->keepAspectRatio != keepAspectRatio || otherImage->size != size || otherImage->path != path || otherImage->opacity != opacity )
if ( !otherImage || otherImage->keepAspectRatio != keepAspectRatio || otherImage->size != size || ( !size.isValid() && otherImage->targetDpi != targetDpi ) || otherImage->opacity != opacity || otherImage->path != path )
return false;

return true;
Expand Down Expand Up @@ -101,7 +102,7 @@ QgsImageCache::QgsImageCache( QObject *parent )
connect( this, &QgsAbstractContentCacheBase::remoteContentFetched, this, &QgsImageCache::remoteImageFetched );
}

QImage QgsImageCache::pathAsImage( const QString &f, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking, bool *isMissing )
QImage QgsImageCache::pathAsImage( const QString &f, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking, double targetDpi, bool *isMissing )
{
const QString file = f.trimmed();
if ( isMissing )
Expand All @@ -114,7 +115,7 @@ QImage QgsImageCache::pathAsImage( const QString &f, const QSize size, const boo

fitsInCache = true;

QgsImageCacheEntry *currentEntry = findExistingEntry( new QgsImageCacheEntry( file, size, keepAspectRatio, opacity ) );
QgsImageCacheEntry *currentEntry = findExistingEntry( new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi ) );

QImage result;

Expand All @@ -125,7 +126,7 @@ QImage QgsImageCache::pathAsImage( const QString &f, const QSize size, const boo
{
long cachedDataSize = 0;
bool isBroken = false;
result = renderImage( file, size, keepAspectRatio, opacity, isBroken, blocking );
result = renderImage( file, size, keepAspectRatio, opacity, targetDpi, isBroken, blocking );
cachedDataSize += result.width() * result.height() * 32;
if ( cachedDataSize > mMaxCacheSize / 2 )
{
Expand Down Expand Up @@ -190,7 +191,7 @@ QSize QgsImageCache::originalSize( const QString &path, bool blocking ) const
return QSize();
}

QImage QgsImageCache::renderImage( const QString &path, QSize size, const bool keepAspectRatio, const double opacity, bool &isBroken, bool blocking ) const
QImage QgsImageCache::renderImage( const QString &path, QSize size, const bool keepAspectRatio, const double opacity, double targetDpi, bool &isBroken, bool blocking ) const
{
QImage im;
isBroken = false;
Expand Down
22 changes: 18 additions & 4 deletions src/core/qgsimagecache.h
Expand Up @@ -45,8 +45,10 @@ class CORE_EXPORT QgsImageCacheEntry : public QgsAbstractContentCacheEntry
*
* If \a keepAspectRatio is TRUE then the original raster aspect ratio will always be preserved
* when resizing.
*
* The \a targetDpi argument is ignored if \a size is a valid size.
*/
QgsImageCacheEntry( const QString &path, QSize size, bool keepAspectRatio, double opacity ) ;
QgsImageCacheEntry( const QString &path, QSize size, bool keepAspectRatio, double opacity, double targetDpi ) ;

//! Rendered image size
QSize size;
Expand All @@ -67,6 +69,13 @@ class CORE_EXPORT QgsImageCacheEntry : public QgsAbstractContentCacheEntry
*/
bool isMissingImage = false;

/**
* Target DPI
*
* \since QGIS 3.22
*/
double targetDpi = 96;

int dataSize() const override;
void dump() const override;
bool isEqual( const QgsAbstractContentCacheEntry *other ) const override;
Expand Down Expand Up @@ -128,11 +137,16 @@ class CORE_EXPORT QgsImageCache : public QgsAbstractContentCache< QgsImageCacheE
* in the same thread to ensure provided the image. WARNING: the \a blocking parameter must NEVER
* be TRUE from GUI based applications (like the main QGIS application) or crashes will result. Only for
* use in external scripts or QGIS server.
*
* Since QGIS 3.22 the \a targetDpi argument can be used to specify an explict DPI to render the image
* at. This is used for some image formats (e.g. PDF) to ensure that content is rendered at the desired
* DPI. This argument is only used when an invalid \a size argument is specified. If a valid \a size is
* specified then the image will always be rendered at this size, regardless of the \a targetDpi.
*/
#ifndef SIP_RUN
QImage pathAsImage( const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache SIP_OUT, bool blocking = false, bool *isMissing = nullptr );
QImage pathAsImage( const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache SIP_OUT, bool blocking = false, double targetDpi = 96, bool *isMissing = nullptr );
#else
QImage pathAsImage( const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache SIP_OUT, bool blocking = false );
QImage pathAsImage( const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache SIP_OUT, bool blocking = false, double targetDpi = 96 );
#endif

/**
Expand Down Expand Up @@ -161,7 +175,7 @@ class CORE_EXPORT QgsImageCache : public QgsAbstractContentCache< QgsImageCacheE

private:

QImage renderImage( const QString &path, QSize size, const bool keepAspectRatio, const double opacity, bool &isBroken, bool blocking = false ) const;
QImage renderImage( const QString &path, QSize size, const bool keepAspectRatio, const double opacity, double targetDpi, bool &isBroken, bool blocking = false ) const;

//! SVG content to be rendered if SVG file was not found.
QByteArray mMissingSvg;
Expand Down
21 changes: 18 additions & 3 deletions tests/src/core/testqgsimagecache.cpp
Expand Up @@ -55,6 +55,7 @@ class TestQgsImageCache : public QObject
void opacity(); // check non-opaque image rendering
void base64();
void empty();
void dpi();

};

Expand Down Expand Up @@ -144,12 +145,12 @@ void TestQgsImageCache::broken()
QgsImageCache cache;
bool inCache = false;
bool missingImage = false;
const QImage img = cache.pathAsImage( QStringLiteral( "bbbbbbb" ), QSize( 200, 200 ), true, 1.0, inCache, false, &missingImage );
const QImage img = cache.pathAsImage( QStringLiteral( "bbbbbbb" ), QSize( 200, 200 ), true, 1.0, inCache, false, 96, &missingImage );
QVERIFY( missingImage );
cache.pathAsImage( QStringLiteral( "bbbbbbb" ), QSize( 200, 200 ), true, 1.0, inCache, false, &missingImage );
cache.pathAsImage( QStringLiteral( "bbbbbbb" ), QSize( 200, 200 ), true, 1.0, inCache, false, 96, &missingImage );
QVERIFY( missingImage );
const QString originalImage = TEST_DATA_DIR + QStringLiteral( "/sample_image.png" );
cache.pathAsImage( originalImage, QSize( 200, 200 ), true, 1.0, inCache, false, &missingImage );
cache.pathAsImage( originalImage, QSize( 200, 200 ), true, 1.0, inCache, false, 96, &missingImage );
QVERIFY( !missingImage );
}

Expand Down Expand Up @@ -303,6 +304,20 @@ void TestQgsImageCache::empty()
QVERIFY( !cache.originalSize( QStringLiteral( " " ) ).isValid() );
}

void TestQgsImageCache::dpi()
{
QgsImageCacheEntry entry1( QStringLiteral( "my path" ), QSize(), true, 1, 96 );
QgsImageCacheEntry entry2( QStringLiteral( "my path" ), QSize(), true, 1, 300 );
QVERIFY( !entry1.isEqual( &entry2 ) );
entry2.targetDpi = 96;
QVERIFY( entry1.isEqual( &entry2 ) );
entry2.targetDpi = 300;
// target dpi is ignored if a valid size is set
entry1.size = QSize( 100, 100 );
entry2.size = QSize( 100, 100 );
QVERIFY( entry1.isEqual( &entry2 ) );
}

bool TestQgsImageCache::imageCheck( const QString &testName, QImage &image, int mismatchCount )
{
//draw background
Expand Down

0 comments on commit 51cb63f

Please sign in to comment.