Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Draw SVG symbol from cached QImage unless it exceeds half of cache si…
…ze, 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
  • Loading branch information
dakcarto authored and mhugent committed Jan 4, 2013
1 parent f815a72 commit 6a936b9
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 62 deletions.
2 changes: 1 addition & 1 deletion python/core/symbology-ng/qgssvgcache.sip
Expand Up @@ -46,7 +46,7 @@ class QgsSvgCache : QObject
~QgsSvgCache();

const QImage& svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor );
double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache );
const QPicture& svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor );

Expand Down
58 changes: 47 additions & 11 deletions src/core/symbology-ng/qgsfillsymbollayerv2.cpp
Expand Up @@ -290,6 +290,7 @@ QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString& svgFilePath, double
mOutlineWidth = 0.3;
mAngle = angle;
setDefaultSvgParams();
mSvgPattern = 0;
}

QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray& svgData, double width, double angle ): QgsImageFillSymbolLayer(), mPatternWidth( width ),
Expand All @@ -300,11 +301,13 @@ QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray& svgData, double
mAngle = angle;
setSubSymbol( new QgsLineSymbolV2() );
setDefaultSvgParams();
mSvgPattern = 0;
}

QgsSVGFillSymbolLayer::~QgsSVGFillSymbolLayer()
{
delete mOutline;
delete mSvgPattern;
}

void QgsSVGFillSymbolLayer::setSvgFilePath( const QString& svgPath )
Expand Down Expand Up @@ -382,22 +385,55 @@ void QgsSVGFillSymbolLayer::startRender( QgsSymbolV2RenderContext& context )
return;
}

int size = context.outputPixelSize( mPatternWidth );
const QImage& patternImage = QgsSvgCache::instance()->svgAsImage( mSvgFilePath, size, mSvgFillColor, mSvgOutlineColor, mSvgOutlineWidth,
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
QTransform brushTransform;
brushTransform.scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
if ( !doubleNear( context.alpha(), 1.0 ) )
if ( mSvgPattern )
{
QImage transparentImage = patternImage.copy();
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
mBrush.setTextureImage( transparentImage );
delete mSvgPattern;
}
mSvgPattern = 0;
double size = context.outputPixelSize( mPatternWidth );

//don't render pattern if symbol size is below one or above 10,000 pixels
if (( int )size < 1.0 || 10000.0 < size )
{
mSvgPattern = new QImage();
mBrush.setTextureImage( *mSvgPattern );
}
else
{
mBrush.setTextureImage( patternImage );
bool fitsInCache = true;
const QImage& patternImage = QgsSvgCache::instance()->svgAsImage( mSvgFilePath, size, mSvgFillColor, mSvgOutlineColor, mSvgOutlineWidth,
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor(), fitsInCache );

if ( !fitsInCache )
{
const QPicture& patternPict = QgsSvgCache::instance()->svgAsPicture( mSvgFilePath, size, mSvgFillColor, mSvgOutlineColor, mSvgOutlineWidth,
context.renderContext().scaleFactor(), 1.0 );
double hwRatio = 1.0;
if ( patternPict.width() > 0 )
{
hwRatio = ( double )patternPict.height() / ( double )patternPict.width();
}
mSvgPattern = new QImage(( int )size, ( int )( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
mSvgPattern->fill( 0 ); // transparent background

QPainter p( mSvgPattern );
p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
}

QTransform brushTransform;
brushTransform.scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
if ( !doubleNear( context.alpha(), 1.0 ) )
{
QImage transparentImage = fitsInCache ? patternImage.copy() : mSvgPattern->copy();
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
mBrush.setTextureImage( transparentImage );
}
else
{
mBrush.setTextureImage( fitsInCache ? patternImage : *mSvgPattern );
}
mBrush.setTransform( brushTransform );
}
mBrush.setTransform( brushTransform );

if ( mOutline )
{
Expand Down
3 changes: 3 additions & 0 deletions src/core/symbology-ng/qgsfillsymbollayerv2.h
Expand Up @@ -152,6 +152,9 @@ class CORE_EXPORT QgsSVGFillSymbolLayer: public QgsImageFillSymbolLayer
QString mSvgFilePath;
/**SVG view box (to keep the aspect ratio */
QRectF mSvgViewBox;
/** SVG pattern image
* @note added in 1.9 */
QImage* mSvgPattern;

//param(fill), param(outline), param(outline-width) are going
//to be replaced in memory
Expand Down
66 changes: 45 additions & 21 deletions src/core/symbology-ng/qgsmarkersymbollayerv2.cpp
Expand Up @@ -637,55 +637,79 @@ void QgsSvgMarkerSymbolLayerV2::renderPoint( const QPointF& point, QgsSymbolV2Re
return;
}

double size = context.outputLineWidth( mSize );
//don't render symbols with size below one or above 10,000 pixels
if (( int )size < 1 || 10000.0 < size )
{
return;
}

p->save();
QPointF outputOffset = QPointF( context.outputLineWidth( mOffset.x() ), context.outputLineWidth( mOffset.y() ) );
if ( mAngle )
outputOffset = _rotatedOffset( outputOffset, mAngle );
p->translate( point + outputOffset );

int size = ( int )( context.outputLineWidth( mSize ) );
if ( size < 1 ) //don't render symbols with size below one pixel
{
return;
}

bool rotated = !doubleNear( mAngle, 0 );
bool drawOnScreen = doubleNear( context.renderContext().rasterScaleFactor(), 1.0, 0.1 );
if ( rotated )
p->rotate( mAngle );

bool fitsInCache = true;
bool usePict = true;
double hwRatio = 1.0;
if ( drawOnScreen && !rotated )
{
usePict = false;
const QImage& img = QgsSvgCache::instance()->svgAsImage( mPath, size, mFillColor, mOutlineColor, mOutlineWidth,
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
//consider transparency
if ( !doubleNear( context.alpha(), 1.0 ) )
{
QImage transparentImage = img.copy();
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
}
else
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor(), fitsInCache );
if ( fitsInCache && img.width() > 1 )
{
p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
//consider transparency
if ( !doubleNear( context.alpha(), 1.0 ) )
{
QImage transparentImage = img.copy();
QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
hwRatio = ( double )transparentImage.height() / ( double )transparentImage.width();
}
else
{
p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
hwRatio = ( double )img.height() / ( double )img.width();
}
}
}
else

if ( usePict || !fitsInCache )
{
p->setOpacity( context.alpha() );
const QPicture& pct = QgsSvgCache::instance()->svgAsPicture( mPath, size, mFillColor, mOutlineColor, mOutlineWidth,
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
p->drawPicture( 0, 0, pct );

if ( pct.width() > 1 )
{
p->drawPicture( 0, 0, pct );
hwRatio = ( double )pct.height() / ( double )pct.width();
}
}

if ( context.selected() )
{
QPen pen( context.selectionColor() );
pen.setWidth( context.outputLineWidth( 1.0 ) );
double penWidth = context.outputLineWidth( 1.0 );
if ( penWidth > size / 20 )
{
// keep the pen width from covering symbol
penWidth = size / 20;
}
double penOffset = penWidth / 2;
pen.setWidth( penWidth );
p->setPen( pen );
p->setBrush( Qt::NoBrush );
double sizePixel = context.outputLineWidth( mSize );
p->drawRect( QRectF( -sizePixel / 2.0, -sizePixel / 2.0, sizePixel, sizePixel ) );
double wSize = size + penOffset;
double hSize = size * hwRatio + penOffset;
p->drawRect( QRectF( -wSize / 2.0, -hSize / 2.0, wSize, hSize ) );
}

p->restore();
Expand Down
105 changes: 84 additions & 21 deletions src/core/symbology-ng/qgssvgcache.cpp
Expand Up @@ -16,6 +16,7 @@
***************************************************************************/

#include "qgssvgcache.h"
#include "qgis.h"
#include "qgslogger.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessagelog.h"
Expand All @@ -33,7 +34,7 @@
#include <QNetworkReply>
#include <QNetworkRequest>

QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0.0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
outline( Qt::black ), image( 0 ), picture( 0 )
{
}
Expand Down Expand Up @@ -107,28 +108,52 @@ QgsSvgCache::~QgsSvgCache()
}


const QImage& QgsSvgCache::svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor )
const QImage& QgsSvgCache::svgAsImage( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache )
{
fitsInCache = true;
QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );

//if current entry image is 0: cache image for entry
// checks to see if image will fit into cache
//update stats for memory usage
if ( !currentEntry->image )
{
cacheImage( currentEntry );
QSvgRenderer r( currentEntry->svgContent );
double hwRatio = 1.0;
if ( r.viewBoxF().width() > 0 )
{
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
}
long cachedDataSize = 0;
cachedDataSize += currentEntry->svgContent.size();
cachedDataSize += ( int )( currentEntry->size * currentEntry->size * hwRatio * 32 );
if ( cachedDataSize > mMaximumSize / 2 )
{
fitsInCache = false;
delete currentEntry->image;
currentEntry->image = 0;
//currentEntry->image = new QImage( 0, 0 );

// instead cache picture
cachePicture( currentEntry );
}
else
{
cacheImage( currentEntry );
}
trimToMaximumSize();
}

return *( currentEntry->image );
}

const QPicture& QgsSvgCache::svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
const QPicture& QgsSvgCache::svgAsPicture( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor )
{
QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );

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

QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor )
{
QgsSvgCacheEntry* entry = new QgsSvgCacheEntry( file, size, outlineWidth, widthScaleFactor, rasterScaleFactor, fill, outline );
Expand Down Expand Up @@ -326,21 +351,38 @@ void QgsSvgCache::cacheImage( QgsSvgCacheEntry* entry )
delete entry->image;
entry->image = 0;

int imageSize = entry->size;
QImage* image = new QImage( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied );
QSvgRenderer r( entry->svgContent );
double hwRatio = 1.0;
if ( r.viewBoxF().width() > 0 )
{
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
}
double wSize = entry->size;
int wImgSize = ( int )wSize;
if ( wImgSize < 1 )
{
wImgSize = 1;
}
double hSize = wSize * hwRatio;
int hImgSize = ( int )hSize;
if ( hImgSize < 1 )
{
hImgSize = 1;
}
// cast double image sizes to int for QImage
QImage* image = new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
image->fill( 0 ); // transparent background

QPainter p( image );
QSvgRenderer r( entry->svgContent );
if ( r.viewBox().width() == r.viewBox().height() )
if ( r.viewBoxF().width() == r.viewBoxF().height() )
{
r.render( &p );
}
else
{
QSize s( r.viewBox().size() );
s.scale( imageSize, imageSize, Qt::KeepAspectRatio );
QRect rect(( imageSize - s.width() ) / 2, ( imageSize - s.height() ) / 2, s.width(), s.height() );
QSizeF s( r.viewBoxF().size() );
s.scale( wSize, hSize, Qt::KeepAspectRatio );
QRectF rect(( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
r.render( &p, rect );
}

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

//correct QPictures dpi correction
QPicture* picture = new QPicture();
double pictureSize = entry->size / 25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor ) * picture->logicalDpiX();
QRectF rect( QPointF( -pictureSize / 2.0, -pictureSize / 2.0 ), QSizeF( pictureSize, pictureSize ) );

QRectF rect;
QSvgRenderer r( entry->svgContent );
double hwRatio = 1.0;
if ( r.viewBoxF().width() > 0 )
{
hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
}
bool drawOnScreen = doubleNear( entry->rasterScaleFactor, 1.0, 0.1 );
if ( drawOnScreen )
{
// fix to ensure rotated symbols scale with composer page (i.e. not map item) zoom
double wSize = entry->size;
double hSize = wSize * hwRatio;
QSizeF s( r.viewBoxF().size() );
s.scale( wSize, hSize, Qt::KeepAspectRatio );
rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
}
else
{
// output for print or image saving @ specific dpi
double scaledSize = entry->size / 25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor );
double wSize = scaledSize * picture->logicalDpiX();
double hSize = scaledSize * picture->logicalDpiY() * r.viewBoxF().height() / r.viewBoxF().width();
rect = QRectF( QPointF( -wSize / 2.0, -hSize / 2.0 ), QSizeF( wSize, hSize ) );
}

QSvgRenderer renderer( entry->svgContent );
QPainter painter( picture );
renderer.render( &painter, rect );
QPainter p( picture );
r.render( &p, rect );
entry->picture = picture;
mTotalSize += entry->picture->size();
}

QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
double widthScaleFactor, double rasterScaleFactor )
{
//search entries in mEntryLookup
Expand Down

0 comments on commit 6a936b9

Please sign in to comment.