Skip to content

Commit

Permalink
[symbology] Fix SVG marker anchor calculation and improve bounds (#39336
Browse files Browse the repository at this point in the history
)
  • Loading branch information
nirvn committed Oct 14, 2020
1 parent 2659c41 commit 1e80643
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 62 deletions.
135 changes: 75 additions & 60 deletions src/core/symbology/qgsmarkersymbollayer.cpp
Expand Up @@ -1965,37 +1965,20 @@ void QgsSvgMarkerSymbolLayer::renderPoint( QPointF point, QgsSymbolRenderContext
return; return;


bool hasDataDefinedSize = false; bool hasDataDefinedSize = false;
double scaledSize = calculateSize( context, hasDataDefinedSize ); double scaledWidth = calculateSize( context, hasDataDefinedSize );
double size = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale ); double width = context.renderContext().convertToPainterUnits( scaledWidth, mSizeUnit, mSizeMapUnitScale );


//don't render symbols with size below one or above 10,000 pixels //don't render symbols with a width below one or above 10,000 pixels
if ( static_cast< int >( size ) < 1 || 10000.0 < size ) if ( static_cast< int >( width ) < 1 || 10000.0 < width )
{ {
return; return;
} }


QgsScopedQPainterState painterState( p ); QgsScopedQPainterState painterState( p );


bool hasDataDefinedAspectRatio = false; bool hasDataDefinedAspectRatio = false;
double aspectRatio = calculateAspectRatio( context, scaledSize, hasDataDefinedAspectRatio ); double aspectRatio = calculateAspectRatio( context, scaledWidth, hasDataDefinedAspectRatio );

double scaledHeight = scaledWidth * ( !qgsDoubleNear( aspectRatio, 0.0 ) ? aspectRatio : mDefaultAspectRatio );
QPointF outputOffset;
double angle = 0.0;
calculateOffsetAndRotation( context, scaledSize, outputOffset, angle );

p->translate( point + outputOffset );

bool rotated = !qgsDoubleNear( angle, 0 );
if ( rotated )
p->rotate( angle );

QString path = mPath;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName ) )
{
context.setOriginalValueVariable( mPath );
path = QgsSymbolLayerUtils::svgSymbolNameToPath( mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyName, context.renderContext().expressionContext(), mPath ),
context.renderContext().pathResolver() );
}


double strokeWidth = mStrokeWidth; double strokeWidth = mStrokeWidth;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) ) if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) )
Expand Down Expand Up @@ -2023,12 +2006,38 @@ void QgsSvgMarkerSymbolLayer::renderPoint( QPointF point, QgsSymbolRenderContext
strokeColor = mDataDefinedProperties.valueAsColor( QgsSymbolLayer::PropertyStrokeColor, context.renderContext().expressionContext(), mStrokeColor ); strokeColor = mDataDefinedProperties.valueAsColor( QgsSymbolLayer::PropertyStrokeColor, context.renderContext().expressionContext(), mStrokeColor );
} }


QString path = mPath;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName ) )
{
context.setOriginalValueVariable( mPath );
path = QgsSymbolLayerUtils::svgSymbolNameToPath( mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyName, context.renderContext().expressionContext(), mPath ),
context.renderContext().pathResolver() );
if ( path != mPath && qgsDoubleNear( aspectRatio, 0.0 ) && !mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyHeight ) )
{
// adjust height of data defined path
QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledWidth, fillColor, strokeColor, strokeWidth,
context.renderContext().scaleFactor(), aspectRatio,
( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) );
scaledHeight = svgViewbox.isValid() ? scaledWidth * svgViewbox.height() / svgViewbox.width() : scaledWidth;
}
}

QPointF outputOffset;
double angle = 0.0;
calculateOffsetAndRotation( context, scaledWidth, scaledHeight, outputOffset, angle );

p->translate( point + outputOffset );

bool rotated = !qgsDoubleNear( angle, 0 );
if ( rotated )
p->rotate( angle );

bool fitsInCache = true; bool fitsInCache = true;
bool usePict = true; bool usePict = true;
bool rasterizeSelected = !mHasFillParam || mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName ); bool rasterizeSelected = !mHasFillParam || mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName );
if ( ( !context.renderContext().forceVectorOutput() && !rotated ) || ( context.selected() && rasterizeSelected ) ) if ( ( !context.renderContext().forceVectorOutput() && !rotated ) || ( context.selected() && rasterizeSelected ) )
{ {
QImage img = QgsApplication::svgCache()->svgAsImage( path, size, fillColor, strokeColor, strokeWidth, QImage img = QgsApplication::svgCache()->svgAsImage( path, width, fillColor, strokeColor, strokeWidth,
context.renderContext().scaleFactor(), fitsInCache, aspectRatio, context.renderContext().scaleFactor(), fitsInCache, aspectRatio,
( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) ); ( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) );
if ( fitsInCache && img.width() > 1 ) if ( fitsInCache && img.width() > 1 )
Expand All @@ -2055,7 +2064,7 @@ void QgsSvgMarkerSymbolLayer::renderPoint( QPointF point, QgsSymbolRenderContext
if ( usePict || !fitsInCache ) if ( usePict || !fitsInCache )
{ {
p->setOpacity( context.opacity() ); p->setOpacity( context.opacity() );
QPicture pct = QgsApplication::svgCache()->svgAsPicture( path, size, fillColor, strokeColor, strokeWidth, QPicture pct = QgsApplication::svgCache()->svgAsPicture( path, width, fillColor, strokeColor, strokeWidth,
context.renderContext().scaleFactor(), context.renderContext().forceVectorOutput(), aspectRatio, context.renderContext().scaleFactor(), context.renderContext().forceVectorOutput(), aspectRatio,
( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) ); ( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) );
if ( pct.width() > 1 ) if ( pct.width() > 1 )
Expand Down Expand Up @@ -2147,12 +2156,12 @@ double QgsSvgMarkerSymbolLayer::calculateAspectRatio( QgsSymbolRenderContext &co
return scaledAspectRatio; return scaledAspectRatio;
} }


void QgsSvgMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledSize, QPointF &offset, double &angle ) const void QgsSvgMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledWidth, double scaledHeight, QPointF &offset, double &angle ) const
{ {
//offset //offset
double offsetX = 0; double offsetX = 0;
double offsetY = 0; double offsetY = 0;
markerOffset( context, scaledSize, scaledSize, offsetX, offsetY ); markerOffset( context, scaledWidth, scaledHeight, offsetX, offsetY );
offset = QPointF( offsetX, offsetY ); offset = QPointF( offsetX, offsetY );


angle = mAngle + mLineAngle; angle = mAngle + mLineAngle;
Expand Down Expand Up @@ -2458,26 +2467,24 @@ bool QgsSvgMarkerSymbolLayer::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFa
QRectF QgsSvgMarkerSymbolLayer::bounds( QPointF point, QgsSymbolRenderContext &context ) QRectF QgsSvgMarkerSymbolLayer::bounds( QPointF point, QgsSymbolRenderContext &context )
{ {
bool hasDataDefinedSize = false; bool hasDataDefinedSize = false;
double scaledSize = calculateSize( context, hasDataDefinedSize ); double scaledWidth = calculateSize( context, hasDataDefinedSize );
scaledSize = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
bool hasDataDefinedAspectRatio = false;
double aspectRatio = calculateAspectRatio( context, scaledWidth, hasDataDefinedAspectRatio );
double scaledHeight = scaledWidth * ( !qgsDoubleNear( aspectRatio, 0.0 ) ? aspectRatio : mDefaultAspectRatio );

scaledWidth = context.renderContext().convertToPainterUnits( scaledWidth, mSizeUnit, mSizeMapUnitScale );
scaledHeight = context.renderContext().convertToPainterUnits( scaledHeight, mSizeUnit, mSizeMapUnitScale );


//don't render symbols with size below one or above 10,000 pixels //don't render symbols with size below one or above 10,000 pixels
if ( static_cast< int >( scaledSize ) < 1 || 10000.0 < scaledSize ) if ( static_cast< int >( scaledWidth ) < 1 || 10000.0 < scaledWidth )
{ {
return QRectF(); return QRectF();
} }


QPointF outputOffset; QPointF outputOffset;
double angle = 0.0; double angle = 0.0;
calculateOffsetAndRotation( context, scaledSize, outputOffset, angle ); calculateOffsetAndRotation( context, scaledWidth, scaledHeight, outputOffset, angle );

QString path = mPath;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName ) )
{
context.setOriginalValueVariable( mPath );
path = QgsSymbolLayerUtils::svgSymbolNameToPath( mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyName, context.renderContext().expressionContext(), mPath ),
context.renderContext().pathResolver() );
}


double strokeWidth = mStrokeWidth; double strokeWidth = mStrokeWidth;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) ) if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) )
Expand All @@ -2487,29 +2494,38 @@ QRectF QgsSvgMarkerSymbolLayer::bounds( QPointF point, QgsSymbolRenderContext &c
} }
strokeWidth = context.renderContext().convertToPainterUnits( strokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale ); strokeWidth = context.renderContext().convertToPainterUnits( strokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale );


//need to get colors to take advantage of cached SVGs QString path = mPath;
QColor fillColor = mColor; if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName ) )
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyFillColor ) )
{
context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mColor ) );
fillColor = mDataDefinedProperties.valueAsColor( QgsSymbolLayer::PropertyFillColor, context.renderContext().expressionContext(), mColor );
}

QColor strokeColor = mStrokeColor;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeColor ) )
{ {
context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mStrokeColor ) ); context.setOriginalValueVariable( mPath );
fillColor = mDataDefinedProperties.valueAsColor( QgsSymbolLayer::PropertyStrokeColor, context.renderContext().expressionContext(), mStrokeColor ); path = QgsSymbolLayerUtils::svgSymbolNameToPath( mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyName, context.renderContext().expressionContext(), mPath ),
} context.renderContext().pathResolver() );
if ( path != mPath && qgsDoubleNear( aspectRatio, 0.0 ) && !mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyHeight ) )
{
// need to get colors to take advantage of cached SVGs
QColor fillColor = mColor;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyFillColor ) )
{
context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mColor ) );
fillColor = mDataDefinedProperties.valueAsColor( QgsSymbolLayer::PropertyFillColor, context.renderContext().expressionContext(), mColor );
}


QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledSize, fillColor, strokeColor, strokeWidth, QColor strokeColor = mStrokeColor;
context.renderContext().scaleFactor(), mFixedAspectRatio, if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeColor ) )
( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) ); {
context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mStrokeColor ) );
fillColor = mDataDefinedProperties.valueAsColor( QgsSymbolLayer::PropertyStrokeColor, context.renderContext().expressionContext(), mStrokeColor );
}


double scaledHeight = svgViewbox.isValid() ? scaledSize * svgViewbox.height() / svgViewbox.width() : scaledSize; // adjust height of data defined path
QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledWidth, fillColor, strokeColor, strokeWidth,
context.renderContext().scaleFactor(), aspectRatio,
( context.renderContext().flags() & QgsRenderContext::RenderBlocking ) );
scaledHeight = svgViewbox.isValid() ? scaledWidth * svgViewbox.height() / svgViewbox.width() : scaledWidth;
}
}


QMatrix transform; QMatrix transform;

// move to the desired position // move to the desired position
transform.translate( point.x() + outputOffset.x(), point.y() + outputOffset.y() ); transform.translate( point.x() + outputOffset.x(), point.y() + outputOffset.y() );


Expand All @@ -2519,17 +2535,16 @@ QRectF QgsSvgMarkerSymbolLayer::bounds( QPointF point, QgsSymbolRenderContext &c
//antialiasing //antialiasing
strokeWidth += 1.0 / 2.0; strokeWidth += 1.0 / 2.0;


QRectF symbolBounds = transform.mapRect( QRectF( -scaledSize / 2.0, QRectF symbolBounds = transform.mapRect( QRectF( -scaledWidth / 2.0,
-scaledHeight / 2.0, -scaledHeight / 2.0,
scaledSize, scaledWidth,
scaledHeight ) ); scaledHeight ) );


//extend bounds by pen width / 2.0 //extend bounds by pen width / 2.0
symbolBounds.adjust( -strokeWidth / 2.0, -strokeWidth / 2.0, symbolBounds.adjust( -strokeWidth / 2.0, -strokeWidth / 2.0,
strokeWidth / 2.0, strokeWidth / 2.0 ); strokeWidth / 2.0, strokeWidth / 2.0 );


return symbolBounds; return symbolBounds;

} }


////////// //////////
Expand Down
2 changes: 1 addition & 1 deletion src/core/symbology/qgsmarkersymbollayer.h
Expand Up @@ -633,7 +633,7 @@ class CORE_EXPORT QgsSvgMarkerSymbolLayer : public QgsMarkerSymbolLayer


private: private:
double calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const; double calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const;
void calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledSize, QPointF &offset, double &angle ) const; void calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledWidth, double scaledHeight, QPointF &offset, double &angle ) const;


}; };


Expand Down
19 changes: 18 additions & 1 deletion tests/src/core/testqgssvgmarker.cpp
Expand Up @@ -58,6 +58,7 @@ class TestQgsSvgMarkerSymbol : public QObject
void bounds(); void bounds();
void boundsWidth(); void boundsWidth();
void bench(); void bench();
void anchor();
void aspectRatio(); void aspectRatio();
void dynamicSizeWithAspectRatio(); void dynamicSizeWithAspectRatio();
void dynamicWidthWithAspectRatio(); void dynamicWidthWithAspectRatio();
Expand Down Expand Up @@ -191,6 +192,22 @@ void TestQgsSvgMarkerSymbol::bench()
QVERIFY( imageCheck( "svgmarker_bench" ) ); QVERIFY( imageCheck( "svgmarker_bench" ) );
} }


void TestQgsSvgMarkerSymbol::anchor()
{
QString svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( QStringLiteral( "/backgrounds/background_square.svg" ), QgsPathResolver() );

mSvgMarkerLayer->setPath( svgPath );
mSvgMarkerLayer->setStrokeColor( Qt::black );
mSvgMarkerLayer->setColor( Qt::black );
mSvgMarkerLayer->setSize( 5 );
mSvgMarkerLayer->setFixedAspectRatio( 6 );
mSvgMarkerLayer->setStrokeWidth( 0.0 );
mSvgMarkerLayer->setVerticalAnchorPoint( QgsMarkerSymbolLayer::Bottom );
QVERIFY( imageCheck( "svgmarker_anchor" ) );
mSvgMarkerLayer->setFixedAspectRatio( 0.0 );
mSvgMarkerLayer->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VCenter );
}

void TestQgsSvgMarkerSymbol::aspectRatio() void TestQgsSvgMarkerSymbol::aspectRatio()
{ {
QString svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( QStringLiteral( "/amenity/amenity_bench.svg" ), QgsPathResolver() ); QString svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( QStringLiteral( "/amenity/amenity_bench.svg" ), QgsPathResolver() );
Expand Down Expand Up @@ -228,7 +245,7 @@ void TestQgsSvgMarkerSymbol::dynamicWidthWithAspectRatio()
mSvgMarkerLayer->setStrokeColor( Qt::black ); mSvgMarkerLayer->setStrokeColor( Qt::black );
mSvgMarkerLayer->setColor( Qt::black ); mSvgMarkerLayer->setColor( Qt::black );
mSvgMarkerLayer->setDataDefinedProperty( QgsSymbolLayer::PropertyWidth, QgsProperty::fromExpression( QStringLiteral( "max(\"importance\" * 5, 10)" ) ) ); mSvgMarkerLayer->setDataDefinedProperty( QgsSymbolLayer::PropertyWidth, QgsProperty::fromExpression( QStringLiteral( "max(\"importance\" * 5, 10)" ) ) );
mSvgMarkerLayer->setFixedAspectRatio( 0.5 ); mSvgMarkerLayer->setFixedAspectRatio( 0.2 );
mSvgMarkerLayer->setStrokeWidth( 0.0 ); mSvgMarkerLayer->setStrokeWidth( 0.0 );


bool result = imageCheck( QStringLiteral( "svgmarker_dynamicwidth_aspectratio" ) ); bool result = imageCheck( QStringLiteral( "svgmarker_dynamicwidth_aspectratio" ) );
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.

0 comments on commit 1e80643

Please sign in to comment.