Skip to content
Permalink
Browse files
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 6a936b936bdbc189293c3e330da9404399034b71
@@ -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 );

@@ -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 ),
@@ -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 )
@@ -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 )
{
@@ -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
@@ -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();
@@ -16,6 +16,7 @@
***************************************************************************/

#include "qgssvgcache.h"
#include "qgis.h"
#include "qgslogger.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessagelog.h"
@@ -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 )
{
}
@@ -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 )
{
@@ -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 );
@@ -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 );
}

@@ -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

0 comments on commit 6a936b9

Please sign in to comment.