Skip to content

Commit

Permalink
Expose some useful API from QgsLegendPatchShape
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Oct 11, 2021
1 parent ca71c92 commit 1f73983
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 37 deletions.
33 changes: 33 additions & 0 deletions python/core/auto_generated/layertree/qgslegendpatchshape.sip.in
Expand Up @@ -110,6 +110,39 @@ it is resized to fit a desired legend patch size.
The default behavior is to respect the :py:func:`~QgsLegendPatchShape.geometry`'s aspect ratio.

.. seealso:: :py:func:`setPreserveAspectRatio`
%End

bool scaleToOutputSize() const;
%Docstring
Returns ``True`` if the patch shape should by resized to the desired target size
when rendering.

Resizing to the target size is the default behavior.

.. seealso:: :py:func:`setScaleToOutputSize`

.. versionadded:: 3.22
%End

void setScaleToOutputSize( bool scale );
%Docstring
Sets whether the patch shape should by resized to the desired target size
when rendering.

Resizing to the target size is the default behavior.

.. seealso:: :py:func:`scaleToOutputSize`

.. versionadded:: 3.22
%End

QgsGeometry scaledGeometry( QSizeF size ) const;
%Docstring
Returns the patch shape's geometry, scaled to the given size.

Note that if :py:func:`~QgsLegendPatchShape.scaleToOutputSize` is ``False`` then no scaling will be applied.

.. versionadded:: 3.22
%End

QList< QList< QPolygonF > > toQPolygonF( Qgis::SymbolType type, QSizeF size ) const;
Expand Down
93 changes: 56 additions & 37 deletions src/core/layertree/qgslegendpatchshape.cpp
Expand Up @@ -54,6 +54,16 @@ void QgsLegendPatchShape::setPreserveAspectRatio( bool preserveAspectRatio )
mPreserveAspectRatio = preserveAspectRatio;
}

bool QgsLegendPatchShape::scaleToOutputSize() const
{
return mScaleToTargetSize;
}

void QgsLegendPatchShape::setScaleToOutputSize( bool scale )
{
mScaleToTargetSize = scale;
}

QPolygonF lineStringToQPolygonF( const QgsLineString *line )
{
const double *srcX = line->xData();
Expand Down Expand Up @@ -81,52 +91,61 @@ QPolygonF curveToPolygonF( const QgsCurve *curve )
}
}

QList<QList<QPolygonF> > QgsLegendPatchShape::toQPolygonF( Qgis::SymbolType type, QSizeF size ) const
QgsGeometry QgsLegendPatchShape::scaledGeometry( QSizeF size ) const
{
if ( isNull() || type != mSymbolType )
return QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( type, size );
QgsGeometry geom = mGeometry;
if ( mScaleToTargetSize )
{
// scale and translate to desired size

// scale and translate to desired size
const QRectF bounds = mGeometry.boundingBox().toRectF();

const QRectF bounds = mGeometry.boundingBox().toRectF();
double dx = 0;
double dy = 0;
if ( mPreserveAspectRatio && bounds.height() > 0 && bounds.width() > 0 )
{
const double scaling = std::min( size.width() / bounds.width(), size.height() / bounds.height() );
const QSizeF scaledSize = bounds.size() * scaling;
dx = ( size.width() - scaledSize.width() ) / 2.0;
dy = ( size.height() - scaledSize.height() ) / 2.0;
size = scaledSize;
}

double dx = 0;
double dy = 0;
if ( mPreserveAspectRatio && bounds.height() > 0 && bounds.width() > 0 )
{
const double scaling = std::min( size.width() / bounds.width(), size.height() / bounds.height() );
const QSizeF scaledSize = bounds.size() * scaling;
dx = ( size.width() - scaledSize.width() ) / 2.0;
dy = ( size.height() - scaledSize.height() ) / 2.0;
size = scaledSize;
}
// important -- the transform needs to flip from north-up to painter style "increasing y down" coordinates
const QPolygonF targetRectPoly = QPolygonF() << QPointF( dx, dy + size.height() )
<< QPointF( dx + size.width(), dy + size.height() )
<< QPointF( dx + size.width(), dy )
<< QPointF( dx, dy );
QTransform t;

// important -- the transform needs to flip from north-up to painter style "increasing y down" coordinates
const QPolygonF targetRectPoly = QPolygonF() << QPointF( dx, dy + size.height() )
<< QPointF( dx + size.width(), dy + size.height() )
<< QPointF( dx + size.width(), dy )
<< QPointF( dx, dy );
QTransform t;
if ( bounds.width() > 0 && bounds.height() > 0 )
{
QPolygonF patchRectPoly = QPolygonF( bounds );
//workaround QT Bug #21329
patchRectPoly.pop_back();

if ( bounds.width() > 0 && bounds.height() > 0 )
{
QPolygonF patchRectPoly = QPolygonF( bounds );
//workaround QT Bug #21329
patchRectPoly.pop_back();
QTransform::quadToQuad( patchRectPoly, targetRectPoly, t );
}
else if ( bounds.width() > 0 )
{
t = QTransform::fromScale( size.width() / bounds.width(), 1 ).translate( -bounds.left(), size.height() / 2 - bounds.y() );
}
else if ( bounds.height() > 0 )
{
t = QTransform::fromScale( 1, size.height() / bounds.height() ).translate( size.width() / 2 - bounds.x(), -bounds.top() );
}

QTransform::quadToQuad( patchRectPoly, targetRectPoly, t );
}
else if ( bounds.width() > 0 )
{
t = QTransform::fromScale( size.width() / bounds.width(), 1 ).translate( -bounds.left(), size.height() / 2 - bounds.y() );
}
else if ( bounds.height() > 0 )
{
t = QTransform::fromScale( 1, size.height() / bounds.height() ).translate( size.width() / 2 - bounds.x(), -bounds.top() );
geom.transform( t );
}
return geom;
}

QgsGeometry geom = mGeometry;
geom.transform( t );
QList<QList<QPolygonF> > QgsLegendPatchShape::toQPolygonF( Qgis::SymbolType type, QSizeF size ) const
{
if ( isNull() || type != mSymbolType )
return QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( type, size );

const QgsGeometry geom = scaledGeometry( size );

switch ( mSymbolType )
{
Expand Down
32 changes: 32 additions & 0 deletions src/core/layertree/qgslegendpatchshape.h
Expand Up @@ -122,6 +122,37 @@ class CORE_EXPORT QgsLegendPatchShape
*/
void setPreserveAspectRatio( bool preserve );

/**
* Returns TRUE if the patch shape should by resized to the desired target size
* when rendering.
*
* Resizing to the target size is the default behavior.
*
* \see setScaleToOutputSize()
* \since QGIS 3.22
*/
bool scaleToOutputSize() const;

/**
* Sets whether the patch shape should by resized to the desired target size
* when rendering.
*
* Resizing to the target size is the default behavior.
*
* \see scaleToOutputSize()
* \since QGIS 3.22
*/
void setScaleToOutputSize( bool scale );

/**
* Returns the patch shape's geometry, scaled to the given size.
*
* Note that if scaleToOutputSize() is FALSE then no scaling will be applied.
*
* \since QGIS 3.22
*/
QgsGeometry scaledGeometry( QSizeF size ) const;

/**
* Converts the patch shape to a set of QPolygonF objects representing
* how the patch should be drawn for a symbol of the given \a type at the specified \a size (as
Expand All @@ -145,6 +176,7 @@ class CORE_EXPORT QgsLegendPatchShape
Qgis::SymbolType mSymbolType = Qgis::SymbolType::Fill;
QgsGeometry mGeometry;
bool mPreserveAspectRatio = true;
bool mScaleToTargetSize = true;

};

Expand Down
16 changes: 16 additions & 0 deletions tests/src/python/test_qgslegendpatchshape.py
Expand Up @@ -71,6 +71,10 @@ def testBasic(self):
shape.setPreserveAspectRatio(True)
self.assertTrue(shape.preserveAspectRatio())

self.assertTrue(shape.scaleToOutputSize())
shape.setScaleToOutputSize(False)
self.assertFalse(shape.scaleToOutputSize())

@staticmethod
def polys_to_list(polys):
return [[[[round(p.x(), 3), round(p.y(), 3)] for p in ring] for ring in poly] for poly in polys]
Expand Down Expand Up @@ -188,6 +192,18 @@ def testFills(self):
self.assertEqual(self.polys_to_list(shape.toQPolygonF(QgsSymbol.Fill, QSizeF(1, 1))), [[[[0.4, 0.667], [0.0, 1.0], [0.2, 0.778], [0.4, 0.667]], [[0.35, 0.722], [0.34, 0.733], [0.35, 0.733], [0.35, 0.722]]], [[[0.9, 0.0], [1.0, 0.0], [1.0, 0.111], [0.9, 0.0]]]])
self.assertEqual(self.polys_to_list(shape.toQPolygonF(QgsSymbol.Fill, QSizeF(10, 2))), [[[[4.0, 1.333], [0.0, 2.0], [2.0, 1.556], [4.0, 1.333]], [[3.5, 1.444], [3.4, 1.467], [3.5, 1.467], [3.5, 1.444]]], [[[9.0, 0.0], [10.0, 0.0], [10.0, 0.222], [9.0, 0.0]]]])

def testScaledGeometry(self):
"""
Test scaling geometry
"""
shape = QgsLegendPatchShape(QgsSymbol.Line, QgsGeometry.fromWkt('LineString(5 5, 1 2)'))

self.assertEqual(shape.scaledGeometry(QSizeF(20, 30)).asWkt(1), 'LineString (20 7.5, 0 22.5)')
self.assertEqual(shape.scaledGeometry(QSizeF(200, 300)).asWkt(1), 'LineString (200 75, 0 225)')
shape.setScaleToOutputSize(False)
self.assertEqual(shape.scaledGeometry(QSizeF(20, 30)).asWkt(1), 'LineString (5 5, 1 2)')
self.assertEqual(shape.scaledGeometry(QSizeF(200, 300)).asWkt(1), 'LineString (5 5, 1 2)')

def testRenderMarker(self):
shape = QgsLegendPatchShape(QgsSymbol.Marker, QgsGeometry.fromWkt('MultiPoint((5 5), (3 4), (1 2))'), False)
rendered_image = self.renderPatch(shape)
Expand Down

0 comments on commit 1f73983

Please sign in to comment.