Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
When rendering symbols with sizes in map units or meters at scale
yet NO scale is available, handle things semi-gracefully

1. Treat map units and meters at map units as millimeter values.
(Any choice we make here is going to be wrong, but this at least
allows us to show map unit based sizes as impacting on symbols
in symbol preview icons)
2. Clamp the resultant size to a reasonable value range. Since
a size of 2000 map units may be perfectly valid for one map
and correspond to ~1mm, on another map this may translate
to a size of 100 cm. We can't know what the expected scale range
is for a pure size value, so we just limit the resultant
size ranges to prevent massive sizes.

Refs #41149 - this prevents the map unit sizes for the effects
from being treated as pixel sizes exceeding 10k sizes.
  • Loading branch information
nyalldawson committed Sep 27, 2021
1 parent 9287772 commit 615f68a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 7 deletions.
34 changes: 27 additions & 7 deletions src/core/qgsrendercontext.cpp
Expand Up @@ -339,6 +339,7 @@ const QgsFeatureFilterProvider *QgsRenderContext::featureFilterProvider() const
double QgsRenderContext::convertToPainterUnits( double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale ) const
{
double conversionFactor = 1.0;
bool isMapUnitHack = false;
switch ( unit )
{
case QgsUnitTypes::RenderMillimeters:
Expand All @@ -355,21 +356,32 @@ double QgsRenderContext::convertToPainterUnits( double size, QgsUnitTypes::Rende

case QgsUnitTypes::RenderMetersInMapUnits:
{
size = convertMetersToMapUnits( size );
if ( mMapToPixel.isValid() )
size = convertMetersToMapUnits( size );
unit = QgsUnitTypes::RenderMapUnits;
// Fall through to RenderMapUnits with size in meters converted to size in MapUnits
FALLTHROUGH
}
case QgsUnitTypes::RenderMapUnits:
{
const double mup = scale.computeMapUnitsPerPixel( *this );
if ( mup > 0 )
if ( mMapToPixel.isValid() )
{
conversionFactor = 1.0 / mup;
const double mup = scale.computeMapUnitsPerPixel( *this );
if ( mup > 0 )
{
conversionFactor = 1.0 / mup;
}
else
{
conversionFactor = 1.0;
}
}
else
{
conversionFactor = 1.0;
// invalid map to pixel. A size in map units can't be calculated, so treat the size as points
// and clamp it to a reasonable range. It's the best we can do in this situation!
isMapUnitHack = true;
conversionFactor = mScaleFactor / POINTS_TO_MM;
}
break;
}
Expand All @@ -395,8 +407,16 @@ double QgsRenderContext::convertToPainterUnits( double size, QgsUnitTypes::Rende
convertedSize = std::min( convertedSize, scale.maxSizeMM * mScaleFactor );
}

const double symbologyReferenceScaleFactor = mSymbologyReferenceScale > 0 ? mSymbologyReferenceScale / mRendererScale : 1;
convertedSize *= symbologyReferenceScaleFactor;
if ( isMapUnitHack )
{
// since we are arbitrarily treating map units as mm, we need to clamp to an (arbitrary!) reasonable range.
convertedSize = std::clamp( convertedSize, 10.0, 100.0 );
}
else
{
const double symbologyReferenceScaleFactor = mSymbologyReferenceScale > 0 ? mSymbologyReferenceScale / mRendererScale : 1;
convertedSize *= symbologyReferenceScaleFactor;
}

return convertedSize;
}
Expand Down
49 changes: 49 additions & 0 deletions tests/src/python/test_qgsrendercontext.py
Expand Up @@ -410,6 +410,55 @@ def testConvertToPainterUnits(self):
size = r.convertToPainterUnits(2, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(size, 2.0, places=5)

def testConvertToPainterUnitsNoMapToPixel(self):
"""
Test converting map unit based sizes to painter units when render context has NO map to pixel set
"""
r = QgsRenderContext()
r.setScaleFactor(300 / 25.4) # 300 dpi, to match above test

# start with no min/max scale
c = QgsMapUnitScale()

# since we have no map scale to work with, this makes the gross assumption that map units == points. It's magic, but
# what else can we do?
size = r.convertToPainterUnits(10, QgsUnitTypes.RenderMapUnits, c)
self.assertAlmostEqual(size, 41.66666, places=3)
size = r.convertToPainterUnits(10, QgsUnitTypes.RenderMetersInMapUnits, c)
self.assertAlmostEqual(size, 41.66666, places=3)

# sizes should be clamped to reasonable range -- we don't want to treat 2000m map unit sizes as 10 million pixels!
size = r.convertToPainterUnits(2000, QgsUnitTypes.RenderMapUnits, c)
self.assertEqual(size, 100.0)
size = r.convertToPainterUnits(2000, QgsUnitTypes.RenderMetersInMapUnits, c)
self.assertEqual(size, 100.0)
size = r.convertToPainterUnits(0.0002, QgsUnitTypes.RenderMapUnits, c)
self.assertEqual(size, 10.0)
size = r.convertToPainterUnits(0.0002, QgsUnitTypes.RenderMetersInMapUnits, c)
self.assertEqual(size, 10.0)

# normal units, should not be affected
size = r.convertToPainterUnits(2, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(size, 23.622047, places=5)
size = r.convertToPainterUnits(2, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(size, 8.33333333125, places=5)
size = r.convertToPainterUnits(2, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(size, 600.0, places=5)
size = r.convertToPainterUnits(2, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(size, 2.0, places=5)

# minimum size greater than the calculated size, so size should be limited to minSizeMM
c.minSizeMM = 5
c.minSizeMMEnabled = True
size = r.convertToPainterUnits(2, QgsUnitTypes.RenderMapUnits, c)
self.assertAlmostEqual(size, 59.0551181, places=5)

# maximum size less than the calculated size, so size should be limited to maxSizeMM
c.maxSizeMM = 6
c.maxSizeMMEnabled = True
size = r.convertToPainterUnits(26, QgsUnitTypes.RenderMapUnits, c)
self.assertAlmostEqual(size, 70.866, places=2)

def testConvertToMapUnits(self):
ms = QgsMapSettings()
ms.setExtent(QgsRectangle(0, 0, 100, 100))
Expand Down

0 comments on commit 615f68a

Please sign in to comment.