diff --git a/python/core/symbology-ng/qgssvgcache.sip b/python/core/symbology-ng/qgssvgcache.sip index 0bfed4950e3..036308f012c 100644 --- a/python/core/symbology-ng/qgssvgcache.sip +++ b/python/core/symbology-ng/qgssvgcache.sip @@ -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 ); diff --git a/src/core/symbology-ng/qgsfillsymbollayerv2.cpp b/src/core/symbology-ng/qgsfillsymbollayerv2.cpp index ddbcebe3249..da1298bbb2a 100644 --- a/src/core/symbology-ng/qgsfillsymbollayerv2.cpp +++ b/src/core/symbology-ng/qgsfillsymbollayerv2.cpp @@ -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 ) { diff --git a/src/core/symbology-ng/qgsfillsymbollayerv2.h b/src/core/symbology-ng/qgsfillsymbollayerv2.h index c14fcdb8d7c..dc28745bb28 100644 --- a/src/core/symbology-ng/qgsfillsymbollayerv2.h +++ b/src/core/symbology-ng/qgsfillsymbollayerv2.h @@ -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 diff --git a/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp b/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp index b431443f096..b3182968d60 100644 --- a/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp +++ b/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp @@ -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 ) ) + context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor(), fitsInCache ); + if ( fitsInCache && img.width() > 1 ) { - QImage transparentImage = img.copy(); - QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() ); - p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage ); - } - else - { - 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(); diff --git a/src/core/symbology-ng/qgssvgcache.cpp b/src/core/symbology-ng/qgssvgcache.cpp index 6c38a202442..9d784138c7a 100644 --- a/src/core/symbology-ng/qgssvgcache.cpp +++ b/src/core/symbology-ng/qgssvgcache.cpp @@ -16,6 +16,7 @@ ***************************************************************************/ #include "qgssvgcache.h" +#include "qgis.h" #include "qgslogger.h" #include "qgsnetworkaccessmanager.h" #include "qgsmessagelog.h" @@ -33,7 +34,7 @@ #include #include -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 diff --git a/src/core/symbology-ng/qgssvgcache.h b/src/core/symbology-ng/qgssvgcache.h index 82d0d7bb6e1..c1f2b49fd50 100644 --- a/src/core/symbology-ng/qgssvgcache.h +++ b/src/core/symbology-ng/qgssvgcache.h @@ -36,7 +36,7 @@ class CORE_EXPORT QgsSvgCacheEntry ~QgsSvgCacheEntry(); QString file; - int size; //size in pixel + double size; //size in pixels (cast to int for QImage) double outlineWidth; double widthScaleFactor; double rasterScaleFactor; @@ -69,9 +69,9 @@ class CORE_EXPORT QgsSvgCache : public QObject static QgsSvgCache* instance(); ~QgsSvgCache(); - const QImage& svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth, - double widthScaleFactor, double rasterScaleFactor ); - const QPicture& svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth, + const QImage& svgAsImage( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth, + double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache ); + const QPicture& svgAsPicture( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor ); /**Tests if an svg file contains parameters for fill, outline color, outline width. If yes, possible default values are returned. If there are several @@ -84,21 +84,21 @@ class CORE_EXPORT QgsSvgCache : public QObject signals: /** Emit a signal to be caught by qgisapp and display a msg on status bar */ - void statusChanged( QString const & theStatusQString ); + void statusChanged( const QString& theStatusQString ); protected: //! protected constructor QgsSvgCache( QObject * parent = 0 ); /**Creates new cache entry and returns pointer to it*/ - QgsSvgCacheEntry* insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth, + QgsSvgCacheEntry* insertSVG( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor ); void replaceParamsAndCacheSvg( QgsSvgCacheEntry* entry ); void cacheImage( QgsSvgCacheEntry* entry ); void cachePicture( QgsSvgCacheEntry* entry ); /**Returns entry from cache or creates a new entry if it does not exist already*/ - QgsSvgCacheEntry* cacheEntry( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth, + QgsSvgCacheEntry* cacheEntry( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor ); /**Removes the least used items until the maximum size is under the limit*/ diff --git a/src/gui/symbology-ng/qgssymbollayerv2widget.cpp b/src/gui/symbology-ng/qgssymbollayerv2widget.cpp index e91caed3ddf..a643ac868e8 100644 --- a/src/gui/symbology-ng/qgssymbollayerv2widget.cpp +++ b/src/gui/symbology-ng/qgssymbollayerv2widget.cpp @@ -558,7 +558,8 @@ class QgsSvgListModel : public QAbstractListModel bool fillParam, outlineParam, outlineWidthParam; QgsSvgCache::instance()->containsParams( entry, fillParam, fill, outlineParam, outline, outlineWidthParam, outlineWidth ); - const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0 ); + bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size) + const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache ); pixmap = QPixmap::fromImage( img ); QPixmapCache::insert( entry, pixmap ); }