Skip to content

Commit 6a936b9

Browse files
dakcartomhugent
authored andcommitted
Draw SVG symbol from cached QImage unless it exceeds half of cache size, then use QPicture
- Fix #6855, SVG markers/fills larger than half cache (559^2 X 32 + SVG) are drawn with QPicture - Fix #6861, make QPicture SVG symbols scale with Composer page zoom - Fix #6861, make SVG symbol output to print/image more accurate by setting 'size' to double - Update/add support for non-squared SVG via QImage and QPicture, on screen and in output to print/image - Non-squared SVG QImage/QPicture can now be used in pattern fill, without excess space
1 parent f815a72 commit 6a936b9

File tree

7 files changed

+189
-62
lines changed

7 files changed

+189
-62
lines changed

python/core/symbology-ng/qgssvgcache.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class QgsSvgCache : QObject
4646
~QgsSvgCache();
4747

4848
const QImage& svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
49-
double widthScaleFactor, double rasterScaleFactor );
49+
double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache );
5050
const QPicture& svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
5151
double widthScaleFactor, double rasterScaleFactor );
5252

src/core/symbology-ng/qgsfillsymbollayerv2.cpp

+47-11
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString& svgFilePath, double
290290
mOutlineWidth = 0.3;
291291
mAngle = angle;
292292
setDefaultSvgParams();
293+
mSvgPattern = 0;
293294
}
294295

295296
QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray& svgData, double width, double angle ): QgsImageFillSymbolLayer(), mPatternWidth( width ),
@@ -300,11 +301,13 @@ QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray& svgData, double
300301
mAngle = angle;
301302
setSubSymbol( new QgsLineSymbolV2() );
302303
setDefaultSvgParams();
304+
mSvgPattern = 0;
303305
}
304306

305307
QgsSVGFillSymbolLayer::~QgsSVGFillSymbolLayer()
306308
{
307309
delete mOutline;
310+
delete mSvgPattern;
308311
}
309312

310313
void QgsSVGFillSymbolLayer::setSvgFilePath( const QString& svgPath )
@@ -382,22 +385,55 @@ void QgsSVGFillSymbolLayer::startRender( QgsSymbolV2RenderContext& context )
382385
return;
383386
}
384387

385-
int size = context.outputPixelSize( mPatternWidth );
386-
const QImage& patternImage = QgsSvgCache::instance()->svgAsImage( mSvgFilePath, size, mSvgFillColor, mSvgOutlineColor, mSvgOutlineWidth,
387-
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
388-
QTransform brushTransform;
389-
brushTransform.scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
390-
if ( !doubleNear( context.alpha(), 1.0 ) )
388+
if ( mSvgPattern )
391389
{
392-
QImage transparentImage = patternImage.copy();
393-
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
394-
mBrush.setTextureImage( transparentImage );
390+
delete mSvgPattern;
391+
}
392+
mSvgPattern = 0;
393+
double size = context.outputPixelSize( mPatternWidth );
394+
395+
//don't render pattern if symbol size is below one or above 10,000 pixels
396+
if (( int )size < 1.0 || 10000.0 < size )
397+
{
398+
mSvgPattern = new QImage();
399+
mBrush.setTextureImage( *mSvgPattern );
395400
}
396401
else
397402
{
398-
mBrush.setTextureImage( patternImage );
403+
bool fitsInCache = true;
404+
const QImage& patternImage = QgsSvgCache::instance()->svgAsImage( mSvgFilePath, size, mSvgFillColor, mSvgOutlineColor, mSvgOutlineWidth,
405+
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor(), fitsInCache );
406+
407+
if ( !fitsInCache )
408+
{
409+
const QPicture& patternPict = QgsSvgCache::instance()->svgAsPicture( mSvgFilePath, size, mSvgFillColor, mSvgOutlineColor, mSvgOutlineWidth,
410+
context.renderContext().scaleFactor(), 1.0 );
411+
double hwRatio = 1.0;
412+
if ( patternPict.width() > 0 )
413+
{
414+
hwRatio = ( double )patternPict.height() / ( double )patternPict.width();
415+
}
416+
mSvgPattern = new QImage(( int )size, ( int )( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
417+
mSvgPattern->fill( 0 ); // transparent background
418+
419+
QPainter p( mSvgPattern );
420+
p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
421+
}
422+
423+
QTransform brushTransform;
424+
brushTransform.scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
425+
if ( !doubleNear( context.alpha(), 1.0 ) )
426+
{
427+
QImage transparentImage = fitsInCache ? patternImage.copy() : mSvgPattern->copy();
428+
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
429+
mBrush.setTextureImage( transparentImage );
430+
}
431+
else
432+
{
433+
mBrush.setTextureImage( fitsInCache ? patternImage : *mSvgPattern );
434+
}
435+
mBrush.setTransform( brushTransform );
399436
}
400-
mBrush.setTransform( brushTransform );
401437

402438
if ( mOutline )
403439
{

src/core/symbology-ng/qgsfillsymbollayerv2.h

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ class CORE_EXPORT QgsSVGFillSymbolLayer: public QgsImageFillSymbolLayer
152152
QString mSvgFilePath;
153153
/**SVG view box (to keep the aspect ratio */
154154
QRectF mSvgViewBox;
155+
/** SVG pattern image
156+
* @note added in 1.9 */
157+
QImage* mSvgPattern;
155158

156159
//param(fill), param(outline), param(outline-width) are going
157160
//to be replaced in memory

src/core/symbology-ng/qgsmarkersymbollayerv2.cpp

+45-21
Original file line numberDiff line numberDiff line change
@@ -637,55 +637,79 @@ void QgsSvgMarkerSymbolLayerV2::renderPoint( const QPointF& point, QgsSymbolV2Re
637637
return;
638638
}
639639

640+
double size = context.outputLineWidth( mSize );
641+
//don't render symbols with size below one or above 10,000 pixels
642+
if (( int )size < 1 || 10000.0 < size )
643+
{
644+
return;
645+
}
646+
640647
p->save();
641648
QPointF outputOffset = QPointF( context.outputLineWidth( mOffset.x() ), context.outputLineWidth( mOffset.y() ) );
642649
if ( mAngle )
643650
outputOffset = _rotatedOffset( outputOffset, mAngle );
644651
p->translate( point + outputOffset );
645652

646-
int size = ( int )( context.outputLineWidth( mSize ) );
647-
if ( size < 1 ) //don't render symbols with size below one pixel
648-
{
649-
return;
650-
}
651-
652653
bool rotated = !doubleNear( mAngle, 0 );
653654
bool drawOnScreen = doubleNear( context.renderContext().rasterScaleFactor(), 1.0, 0.1 );
654655
if ( rotated )
655656
p->rotate( mAngle );
656657

658+
bool fitsInCache = true;
659+
bool usePict = true;
660+
double hwRatio = 1.0;
657661
if ( drawOnScreen && !rotated )
658662
{
663+
usePict = false;
659664
const QImage& img = QgsSvgCache::instance()->svgAsImage( mPath, size, mFillColor, mOutlineColor, mOutlineWidth,
660-
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
661-
//consider transparency
662-
if ( !doubleNear( context.alpha(), 1.0 ) )
663-
{
664-
QImage transparentImage = img.copy();
665-
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
666-
p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
667-
}
668-
else
665+
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor(), fitsInCache );
666+
if ( fitsInCache && img.width() > 1 )
669667
{
670-
p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
668+
//consider transparency
669+
if ( !doubleNear( context.alpha(), 1.0 ) )
670+
{
671+
QImage transparentImage = img.copy();
672+
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
673+
p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
674+
hwRatio = ( double )transparentImage.height() / ( double )transparentImage.width();
675+
}
676+
else
677+
{
678+
p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
679+
hwRatio = ( double )img.height() / ( double )img.width();
680+
}
671681
}
672682
}
673-
else
683+
684+
if ( usePict || !fitsInCache )
674685
{
675686
p->setOpacity( context.alpha() );
676687
const QPicture& pct = QgsSvgCache::instance()->svgAsPicture( mPath, size, mFillColor, mOutlineColor, mOutlineWidth,
677688
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
678-
p->drawPicture( 0, 0, pct );
689+
690+
if ( pct.width() > 1 )
691+
{
692+
p->drawPicture( 0, 0, pct );
693+
hwRatio = ( double )pct.height() / ( double )pct.width();
694+
}
679695
}
680696

681697
if ( context.selected() )
682698
{
683699
QPen pen( context.selectionColor() );
684-
pen.setWidth( context.outputLineWidth( 1.0 ) );
700+
double penWidth = context.outputLineWidth( 1.0 );
701+
if ( penWidth > size / 20 )
702+
{
703+
// keep the pen width from covering symbol
704+
penWidth = size / 20;
705+
}
706+
double penOffset = penWidth / 2;
707+
pen.setWidth( penWidth );
685708
p->setPen( pen );
686709
p->setBrush( Qt::NoBrush );
687-
double sizePixel = context.outputLineWidth( mSize );
688-
p->drawRect( QRectF( -sizePixel / 2.0, -sizePixel / 2.0, sizePixel, sizePixel ) );
710+
double wSize = size + penOffset;
711+
double hSize = size * hwRatio + penOffset;
712+
p->drawRect( QRectF( -wSize / 2.0, -hSize / 2.0, wSize, hSize ) );
689713
}
690714

691715
p->restore();

src/core/symbology-ng/qgssvgcache.cpp

+84-21
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
***************************************************************************/
1717

1818
#include "qgssvgcache.h"
19+
#include "qgis.h"
1920
#include "qgslogger.h"
2021
#include "qgsnetworkaccessmanager.h"
2122
#include "qgsmessagelog.h"
@@ -33,7 +34,7 @@
3334
#include <QNetworkReply>
3435
#include <QNetworkRequest>
3536

36-
QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
37+
QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0.0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
3738
outline( Qt::black ), image( 0 ), picture( 0 )
3839
{
3940
}
@@ -107,28 +108,52 @@ QgsSvgCache::~QgsSvgCache()
107108
}
108109

109110

110-
const QImage& QgsSvgCache::svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
111-
double widthScaleFactor, double rasterScaleFactor )
111+
const QImage& QgsSvgCache::svgAsImage( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
112+
double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache )
112113
{
114+
fitsInCache = true;
113115
QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
114116

115117
//if current entry image is 0: cache image for entry
118+
// checks to see if image will fit into cache
116119
//update stats for memory usage
117120
if ( !currentEntry->image )
118121
{
119-
cacheImage( currentEntry );
122+
QSvgRenderer r( currentEntry->svgContent );
123+
double hwRatio = 1.0;
124+
if ( r.viewBoxF().width() > 0 )
125+
{
126+
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
127+
}
128+
long cachedDataSize = 0;
129+
cachedDataSize += currentEntry->svgContent.size();
130+
cachedDataSize += ( int )( currentEntry->size * currentEntry->size * hwRatio * 32 );
131+
if ( cachedDataSize > mMaximumSize / 2 )
132+
{
133+
fitsInCache = false;
134+
delete currentEntry->image;
135+
currentEntry->image = 0;
136+
//currentEntry->image = new QImage( 0, 0 );
137+
138+
// instead cache picture
139+
cachePicture( currentEntry );
140+
}
141+
else
142+
{
143+
cacheImage( currentEntry );
144+
}
120145
trimToMaximumSize();
121146
}
122147

123148
return *( currentEntry->image );
124149
}
125150

126-
const QPicture& QgsSvgCache::svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
151+
const QPicture& QgsSvgCache::svgAsPicture( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
127152
double widthScaleFactor, double rasterScaleFactor )
128153
{
129154
QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
130155

131-
//if current entry image is 0: cache image for entry
156+
//if current entry picture is 0: cache picture for entry
132157
//update stats for memory usage
133158
if ( !currentEntry->picture )
134159
{
@@ -139,7 +164,7 @@ const QPicture& QgsSvgCache::svgAsPicture( const QString& file, int size, const
139164
return *( currentEntry->picture );
140165
}
141166

142-
QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
167+
QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
143168
double widthScaleFactor, double rasterScaleFactor )
144169
{
145170
QgsSvgCacheEntry* entry = new QgsSvgCacheEntry( file, size, outlineWidth, widthScaleFactor, rasterScaleFactor, fill, outline );
@@ -326,21 +351,38 @@ void QgsSvgCache::cacheImage( QgsSvgCacheEntry* entry )
326351
delete entry->image;
327352
entry->image = 0;
328353

329-
int imageSize = entry->size;
330-
QImage* image = new QImage( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied );
354+
QSvgRenderer r( entry->svgContent );
355+
double hwRatio = 1.0;
356+
if ( r.viewBoxF().width() > 0 )
357+
{
358+
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
359+
}
360+
double wSize = entry->size;
361+
int wImgSize = ( int )wSize;
362+
if ( wImgSize < 1 )
363+
{
364+
wImgSize = 1;
365+
}
366+
double hSize = wSize * hwRatio;
367+
int hImgSize = ( int )hSize;
368+
if ( hImgSize < 1 )
369+
{
370+
hImgSize = 1;
371+
}
372+
// cast double image sizes to int for QImage
373+
QImage* image = new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
331374
image->fill( 0 ); // transparent background
332375

333376
QPainter p( image );
334-
QSvgRenderer r( entry->svgContent );
335-
if ( r.viewBox().width() == r.viewBox().height() )
377+
if ( r.viewBoxF().width() == r.viewBoxF().height() )
336378
{
337379
r.render( &p );
338380
}
339381
else
340382
{
341-
QSize s( r.viewBox().size() );
342-
s.scale( imageSize, imageSize, Qt::KeepAspectRatio );
343-
QRect rect(( imageSize - s.width() ) / 2, ( imageSize - s.height() ) / 2, s.width(), s.height() );
383+
QSizeF s( r.viewBoxF().size() );
384+
s.scale( wSize, hSize, Qt::KeepAspectRatio );
385+
QRectF rect(( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
344386
r.render( &p, rect );
345387
}
346388

@@ -360,18 +402,39 @@ void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry )
360402

361403
//correct QPictures dpi correction
362404
QPicture* picture = new QPicture();
363-
double pictureSize = entry->size / 25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor ) * picture->logicalDpiX();
364-
QRectF rect( QPointF( -pictureSize / 2.0, -pictureSize / 2.0 ), QSizeF( pictureSize, pictureSize ) );
365-
405+
QRectF rect;
406+
QSvgRenderer r( entry->svgContent );
407+
double hwRatio = 1.0;
408+
if ( r.viewBoxF().width() > 0 )
409+
{
410+
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
411+
}
412+
bool drawOnScreen = doubleNear( entry->rasterScaleFactor, 1.0, 0.1 );
413+
if ( drawOnScreen )
414+
{
415+
// fix to ensure rotated symbols scale with composer page (i.e. not map item) zoom
416+
double wSize = entry->size;
417+
double hSize = wSize * hwRatio;
418+
QSizeF s( r.viewBoxF().size() );
419+
s.scale( wSize, hSize, Qt::KeepAspectRatio );
420+
rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
421+
}
422+
else
423+
{
424+
// output for print or image saving @ specific dpi
425+
double scaledSize = entry->size / 25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor );
426+
double wSize = scaledSize * picture->logicalDpiX();
427+
double hSize = scaledSize * picture->logicalDpiY() * r.viewBoxF().height() / r.viewBoxF().width();
428+
rect = QRectF( QPointF( -wSize / 2.0, -hSize / 2.0 ), QSizeF( wSize, hSize ) );
429+
}
366430

367-
QSvgRenderer renderer( entry->svgContent );
368-
QPainter painter( picture );
369-
renderer.render( &painter, rect );
431+
QPainter p( picture );
432+
r.render( &p, rect );
370433
entry->picture = picture;
371434
mTotalSize += entry->picture->size();
372435
}
373436

374-
QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
437+
QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
375438
double widthScaleFactor, double rasterScaleFactor )
376439
{
377440
//search entries in mEntryLookup

0 commit comments

Comments
 (0)