Skip to content

Commit 942f431

Browse files
committedOct 30, 2017
Fix crash when rendering with SVG based symbols and SVG cache is filled
When the svg cache was full, any attempt to render an svg from the cache would crash QGIS. Fixes #16643
1 parent b3ef4da commit 942f431

File tree

4 files changed

+161
-32
lines changed

4 files changed

+161
-32
lines changed
 

‎src/core/symbology/qgssvgcache.cpp

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ QImage QgsSvgCache::svgAsImage( const QString &file, double size, const QColor &
101101
fitsInCache = true;
102102
QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio );
103103

104+
QImage result;
105+
104106
//if current entry image is 0: cache image for entry
105107
// checks to see if image will fit into cache
106108
//update stats for memory usage
@@ -134,15 +136,23 @@ QImage QgsSvgCache::svgAsImage( const QString &file, double size, const QColor &
134136
{
135137
cachePicture( currentEntry, false );
136138
}
139+
140+
// ...and render cached picture to result image
141+
result = imageFromCachedPicture( *currentEntry );
137142
}
138143
else
139144
{
140145
cacheImage( currentEntry );
146+
result = *( currentEntry->image );
141147
}
142148
trimToMaximumSize();
143149
}
150+
else
151+
{
152+
result = *( currentEntry->image );
153+
}
144154

145-
return *( currentEntry->image );
155+
return result;
146156
}
147157

148158
QPicture QgsSvgCache::svgAsPicture( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
@@ -479,47 +489,25 @@ void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
479489
delete entry->image;
480490
entry->image = nullptr;
481491

482-
bool isFixedAR = entry->fixedAspectRatio > 0;
492+
QSizeF viewBoxSize;
493+
QSizeF scaledSize;
494+
QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
483495

484-
QSvgRenderer r( entry->svgContent );
485-
double hwRatio = 1.0;
486-
if ( r.viewBoxF().width() > 0 )
487-
{
488-
if ( isFixedAR )
489-
{
490-
hwRatio = entry->fixedAspectRatio;
491-
}
492-
else
493-
{
494-
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
495-
}
496-
}
497-
double wSize = entry->size;
498-
int wImgSize = static_cast< int >( wSize );
499-
if ( wImgSize < 1 )
500-
{
501-
wImgSize = 1;
502-
}
503-
double hSize = wSize * hwRatio;
504-
int hImgSize = static_cast< int >( hSize );
505-
if ( hImgSize < 1 )
506-
{
507-
hImgSize = 1;
508-
}
509496
// cast double image sizes to int for QImage
510-
QImage *image = new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
497+
QImage *image = new QImage( imageSize, QImage::Format_ARGB32_Premultiplied );
511498
image->fill( 0 ); // transparent background
512499

513500
QPainter p( image );
514-
if ( qgsDoubleNear( r.viewBoxF().width(), r.viewBoxF().height() ) )
501+
QSvgRenderer r( entry->svgContent );
502+
if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
515503
{
516504
r.render( &p );
517505
}
518506
else
519507
{
520-
QSizeF s( r.viewBoxF().size() );
521-
s.scale( wSize, hSize, Qt::KeepAspectRatio );
522-
QRectF rect( ( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
508+
QSizeF s( viewBoxSize );
509+
s.scale( scaledSize.width(), scaledSize.height(), Qt::KeepAspectRatio );
510+
QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
523511
r.render( &p, rect );
524512
}
525513

@@ -906,6 +894,53 @@ void QgsSvgCache::printEntryList()
906894
}
907895
}
908896

897+
QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
898+
{
899+
bool isFixedAR = entry.fixedAspectRatio > 0;
900+
901+
QSvgRenderer r( entry.svgContent );
902+
double hwRatio = 1.0;
903+
viewBoxSize = r.viewBoxF().size();
904+
if ( viewBoxSize.width() > 0 )
905+
{
906+
if ( isFixedAR )
907+
{
908+
hwRatio = entry.fixedAspectRatio;
909+
}
910+
else
911+
{
912+
hwRatio = viewBoxSize.height() / viewBoxSize.width();
913+
}
914+
}
915+
916+
// cast double image sizes to int for QImage
917+
scaledSize.setWidth( entry.size );
918+
int wImgSize = static_cast< int >( scaledSize.width() );
919+
if ( wImgSize < 1 )
920+
{
921+
wImgSize = 1;
922+
}
923+
scaledSize.setHeight( scaledSize.width() * hwRatio );
924+
int hImgSize = static_cast< int >( scaledSize.height() );
925+
if ( hImgSize < 1 )
926+
{
927+
hImgSize = 1;
928+
}
929+
return QSize( wImgSize, hImgSize );
930+
}
931+
932+
QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
933+
{
934+
QSizeF viewBoxSize;
935+
QSizeF scaledSize;
936+
QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
937+
image.fill( 0 ); // transparent background
938+
939+
QPainter p( &image );
940+
p.drawPicture( QPoint( 0, 0 ), *entry.picture );
941+
return image;
942+
}
943+
909944
void QgsSvgCache::trimToMaximumSize()
910945
{
911946
//only one entry in cache

‎src/core/symbology/qgssvgcache.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,17 @@ class CORE_EXPORT QgsSvgCache : public QObject
275275
//! For debugging
276276
void printEntryList();
277277

278+
/**
279+
* Returns the target size (in pixels) and calculates the \a viewBoxSize
280+
* for a cache \a entry.
281+
*/
282+
QSize sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const;
283+
284+
/**
285+
* Returns a rendered image for a cached picture \a entry.
286+
*/
287+
QImage imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const;
288+
278289
//! SVG content to be rendered if SVG file was not found.
279290
QByteArray mMissingSvg;
280291

‎tests/src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ SET(TESTS
180180
testqgsstatisticalsummary.cpp
181181
testqgsstringutils.cpp
182182
testqgsstyle.cpp
183+
testqgssvgcache.cpp
183184
testqgssvgmarker.cpp
184185
testqgssymbol.cpp
185186
testqgstaskmanager.cpp

‎tests/src/core/testqgssvgcache.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/***************************************************************************
2+
testqgssvgcache.cpp
3+
--------------------
4+
Date : October 2017
5+
Copyright : (C) 2017 by Nyall Dawson
6+
Email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#include "qgstest.h"
16+
#include <QObject>
17+
#include <QString>
18+
#include <QStringList>
19+
#include <QApplication>
20+
#include <QFileInfo>
21+
#include <QDir>
22+
#include <QDesktopServices>
23+
24+
#include "qgssvgcache.h"
25+
26+
/**
27+
* \ingroup UnitTests
28+
* This is a unit test for QgsSvgCache.
29+
*/
30+
class TestQgsSvgCache : public QObject
31+
{
32+
Q_OBJECT
33+
34+
public:
35+
TestQgsSvgCache() = default;
36+
37+
private slots:
38+
void initTestCase();// will be called before the first testfunction is executed.
39+
void cleanupTestCase();// will be called after the last testfunction was executed.
40+
void init() {} // will be called before each testfunction is executed.
41+
void cleanup() {} // will be called after every testfunction.
42+
void fillCache();
43+
44+
};
45+
46+
47+
void TestQgsSvgCache::initTestCase()
48+
{
49+
QgsApplication::init();
50+
QgsApplication::initQgis();
51+
}
52+
53+
void TestQgsSvgCache::cleanupTestCase()
54+
{
55+
}
56+
57+
void TestQgsSvgCache::fillCache()
58+
{
59+
QgsSvgCache cache;
60+
// flood cache to fill it
61+
QString svgPath = TEST_DATA_DIR + QStringLiteral( "/sample_svg.svg" );
62+
bool fitInCache = false;
63+
64+
// we loop forever, continually increasing the size of the requested
65+
// svg render. The continually changing image size should quickly fill
66+
// the svg cache size, forcing use of non-cached images.
67+
// We break after hitting a certain threshold of non-cached images,
68+
// (after testing that the result is non-null, i.e. rendered on demand,
69+
// not from cache).
70+
int uncached = 0;
71+
for ( double size = 1000; uncached < 10; size += 100 )
72+
{
73+
QImage image = cache.svgAsImage( svgPath, size, QColor( 255, 0, 0 ), QColor( 0, 255, 0 ), 1, 1, fitInCache );
74+
QVERIFY( !image.isNull() );
75+
if ( !fitInCache )
76+
uncached++;
77+
}
78+
}
79+
80+
81+
QGSTEST_MAIN( TestQgsSvgCache )
82+
#include "testqgssvgcache.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.