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
This commit is contained in:
Larry Shaffer 2012-12-09 14:09:22 -07:00 committed by Marco Hugentobler
parent f815a72b6b
commit 6a936b936b
7 changed files with 189 additions and 62 deletions

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

@ -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 );
}