Skip to content

Commit 7a3abbd

Browse files
committedJan 13, 2023
Add tests
1 parent bf1c4f8 commit 7a3abbd

File tree

7 files changed

+159
-87
lines changed

7 files changed

+159
-87
lines changed
 

‎python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Returns the map unit scale for the fill's offset.
186186

187187
virtual Qt::BrushStyle dxfBrushStyle() const;
188188

189-
virtual QImage toTiledPattern( ) const;
189+
virtual QImage toTiledPatternImage( ) const;
190190

191191

192192
protected:
@@ -1549,7 +1549,7 @@ ownership of the returned object.
15491549

15501550
virtual void toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const;
15511551

1552-
virtual QImage toTiledPattern( ) const;
1552+
virtual QImage toTiledPatternImage( ) const;
15531553

15541554
virtual double estimateMaxBleed( const QgsRenderContext &context ) const;
15551555

@@ -1846,7 +1846,7 @@ Caller takes ownership of the returned symbol layer.
18461846

18471847
virtual void toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const;
18481848

1849-
virtual QImage toTiledPattern( ) const;
1849+
virtual QImage toTiledPatternImage( ) const;
18501850

18511851
virtual double estimateMaxBleed( const QgsRenderContext &context ) const;
18521852

‎python/core/auto_generated/symbology/qgssymbollayer.sip.in

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,9 +1313,12 @@ The ``rings`` argument optionally specifies a list of polygon rings to render as
13131313
void setAngle( double angle );
13141314
double angle() const;
13151315

1316-
virtual QImage toTiledPattern( ) const;
1316+
virtual QImage toTiledPatternImage( ) const;
13171317
%Docstring
1318-
Renders the symbol layer to an image that can be used as a seamless pattern fill.
1318+
Renders the symbol layer to an image that can be used as a seamless pattern fill
1319+
for polygons, this method is used by SLD export to generate image tiles for
1320+
ExternalGraphic polygon fills.
1321+
13191322
The default implementation returns a null image.
13201323

13211324
:return: the tile image (not necessarily a square) or a null image if not implemented.

‎src/core/symbology/qgsfillsymbollayer.cpp

Lines changed: 80 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, c
413413
bool exportOk { false };
414414
if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) && mBrush.style() != Qt::NoBrush )
415415
{
416-
const QImage image { toTiledPattern( ) };
416+
const QImage image { toTiledPatternImage( ) };
417417
if ( ! image.isNull() )
418418
{
419419
QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
@@ -570,10 +570,8 @@ Qt::BrushStyle QgsSimpleFillSymbolLayer::dxfBrushStyle() const
570570
return mBrushStyle;
571571
}
572572

573-
QImage QgsSimpleFillSymbolLayer::toTiledPattern( ) const
573+
QImage QgsSimpleFillSymbolLayer::toTiledPatternImage( ) const
574574
{
575-
// TODO: calculate size, e.g. for solid brush a 1x1 image is sufficient
576-
// for other brush styles must be calculated.
577575
QPixmap pixmap( QSize( 32, 32 ) );
578576
pixmap.fill( Qt::transparent );
579577
QPainter painter;
@@ -2640,7 +2638,7 @@ void QgsLinePatternFillSymbolLayer::stopFeatureRender( const QgsFeature &, QgsRe
26402638
// deliberately don't pass this on to subsymbol here
26412639
}
26422640

2643-
QImage QgsLinePatternFillSymbolLayer::toTiledPattern() const
2641+
QImage QgsLinePatternFillSymbolLayer::toTiledPatternImage() const
26442642
{
26452643

26462644
double lineAngleRads { qDegreesToRadians( mLineAngle ) };
@@ -3413,7 +3411,7 @@ void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &eleme
34133411
bool exportOk { false };
34143412
if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
34153413
{
3416-
const QImage image { toTiledPattern( ) };
3414+
const QImage image { toTiledPatternImage() };
34173415
if ( ! image.isNull() )
34183416
{
34193417
const QFileInfo info { context.exportFilePath() };
@@ -4361,7 +4359,7 @@ void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &elem
43614359
bool exportOk { false };
43624360
if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
43634361
{
4364-
const QImage image { toTiledPattern( ) };
4362+
const QImage image { toTiledPatternImage( ) };
43654363
if ( ! image.isNull() )
43664364
{
43674365
QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
@@ -4408,7 +4406,7 @@ void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &elem
44084406
}
44094407
}
44104408

4411-
QImage QgsPointPatternFillSymbolLayer::toTiledPattern() const
4409+
QImage QgsPointPatternFillSymbolLayer::toTiledPatternImage() const
44124410
{
44134411

44144412
double angleRads { qDegreesToRadians( mAngle ) };
@@ -4472,92 +4470,102 @@ QgsSymbolLayer *QgsPointPatternFillSymbolLayer::createFromSld( QDomElement &elem
44724470
std::unique_ptr< QgsPointPatternFillSymbolLayer > pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
44734471
pointPatternFillSl->setSubSymbol( marker.release() );
44744472

4475-
// Set distance X and Y from vendor options
4476-
QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4477-
for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4473+
auto distanceParser = [ & ]( const QStringList & values )
44784474
{
4479-
if ( it.key() == QLatin1String( "graphic-margin" ) )
4480-
{
4481-
4482-
// This may not be correct in all cases, TODO: check "uom"
4483-
pointPatternFillSl->setDistanceXUnit( QgsUnitTypes::RenderUnit::RenderPixels );
4484-
pointPatternFillSl->setDistanceYUnit( QgsUnitTypes::RenderUnit::RenderPixels );
44854475

4486-
const QStringList values = it.value().split( ' ' );
4476+
// This may not be correct in all cases, TODO: check "uom"
4477+
pointPatternFillSl->setDistanceXUnit( QgsUnitTypes::RenderUnit::RenderPixels );
4478+
pointPatternFillSl->setDistanceYUnit( QgsUnitTypes::RenderUnit::RenderPixels );
44874479

4488-
switch ( values.count( ) )
4480+
switch ( values.count( ) )
4481+
{
4482+
case 1: // top-right-bottom-left (single value for all four margins)
44894483
{
4490-
case 1: // top-right-bottom-left (single value for all four margins)
4484+
bool ok;
4485+
const double v { values.at( 0 ).toDouble( &ok ) };
4486+
if ( ok )
44914487
{
4492-
bool ok;
4493-
const double v { values.at( 0 ).toDouble( &ok ) };
4494-
if ( ok )
4495-
{
4496-
pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4497-
pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4498-
}
4499-
break;
4488+
pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4489+
pointPatternFillSl->setDistanceY( v * 2 + markerSize );
45004490
}
4501-
case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4491+
break;
4492+
}
4493+
case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4494+
{
4495+
bool ok;
4496+
const double vX { values.at( 1 ).toDouble( &ok ) };
4497+
if ( ok )
45024498
{
4503-
bool ok;
4504-
const double vX { values.at( 1 ).toDouble( &ok ) };
4505-
if ( ok )
4506-
{
4507-
pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4508-
}
4509-
const double vY { values.at( 0 ).toDouble( &ok ) };
4510-
if ( ok )
4511-
{
4512-
pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4513-
}
4514-
break;
4499+
pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
45154500
}
4516-
case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4501+
const double vY { values.at( 0 ).toDouble( &ok ) };
4502+
if ( ok )
45174503
{
4518-
bool ok;
4519-
const double vX { values.at( 1 ).toDouble( &ok ) };
4520-
if ( ok )
4521-
{
4522-
pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4523-
}
4524-
const double vYt { values.at( 0 ).toDouble( &ok ) };
4504+
pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4505+
}
4506+
break;
4507+
}
4508+
case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4509+
{
4510+
bool ok;
4511+
const double vX { values.at( 1 ).toDouble( &ok ) };
4512+
if ( ok )
4513+
{
4514+
pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4515+
}
4516+
const double vYt { values.at( 0 ).toDouble( &ok ) };
4517+
if ( ok )
4518+
{
4519+
const double vYb { values.at( 2 ).toDouble( &ok ) };
45254520
if ( ok )
45264521
{
4527-
const double vYb { values.at( 2 ).toDouble( &ok ) };
4528-
if ( ok )
4529-
{
4530-
pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4531-
}
4522+
pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
45324523
}
4533-
break;
45344524
}
4535-
case 4: // top,right,bottom,left (one explicit value per margin)
4525+
break;
4526+
}
4527+
case 4: // top,right,bottom,left (one explicit value per margin)
4528+
{
4529+
bool ok;
4530+
const double vYt { values.at( 0 ).toDouble( &ok ) };
4531+
if ( ok )
45364532
{
4537-
bool ok;
4538-
const double vYt { values.at( 0 ).toDouble( &ok ) };
4533+
const double vYb { values.at( 2 ).toDouble( &ok ) };
45394534
if ( ok )
45404535
{
4541-
const double vYb { values.at( 2 ).toDouble( &ok ) };
4542-
if ( ok )
4543-
{
4544-
pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4545-
}
4536+
pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
45464537
}
4547-
const double vXr { values.at( 1 ).toDouble( &ok ) };
4538+
}
4539+
const double vXr { values.at( 1 ).toDouble( &ok ) };
4540+
if ( ok )
4541+
{
4542+
const double vXl { values.at( 3 ).toDouble( &ok ) };
45484543
if ( ok )
45494544
{
4550-
const double vXl { values.at( 3 ).toDouble( &ok ) };
4551-
if ( ok )
4552-
{
4553-
pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4554-
}
4545+
pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
45554546
}
4556-
break;
45574547
}
4558-
default:
4559-
break;
4548+
break;
45604549
}
4550+
default:
4551+
break;
4552+
}
4553+
};
4554+
4555+
// Set distance X and Y from vendor options
4556+
QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4557+
for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4558+
{
4559+
// Legacy
4560+
if ( it.key() == QLatin1String( "distance" ) )
4561+
{
4562+
distanceParser( it.value().split( ',' ) );
4563+
}
4564+
// GeoServer
4565+
else if ( it.key() == QLatin1String( "graphic-margin" ) )
4566+
{
4567+
distanceParser( it.value().split( ' ' ) );
4568+
45614569
}
45624570
}
45634571
return pointPatternFillSl.release();

‎src/core/symbology/qgsfillsymbollayer.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class CORE_EXPORT QgsSimpleFillSymbolLayer : public QgsFillSymbolLayer
179179
Qt::PenStyle dxfPenStyle() const override;
180180
QColor dxfBrushColor( QgsSymbolRenderContext &context ) const override;
181181
Qt::BrushStyle dxfBrushStyle() const override;
182-
QImage toTiledPattern( ) const override;
182+
QImage toTiledPatternImage( ) const override;
183183

184184
protected:
185185
QBrush mBrush;
@@ -1419,7 +1419,7 @@ class CORE_EXPORT QgsLinePatternFillSymbolLayer: public QgsImageFillSymbolLayer
14191419
QVariantMap properties() const override;
14201420
QgsLinePatternFillSymbolLayer *clone() const override SIP_FACTORY;
14211421
void toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const override;
1422-
QImage toTiledPattern( ) const override;
1422+
QImage toTiledPatternImage( ) const override;
14231423
double estimateMaxBleed( const QgsRenderContext &context ) const override;
14241424

14251425
QString ogrFeatureStyleWidth( double widthScaleFactor ) const;
@@ -1691,7 +1691,7 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer
16911691
QVariantMap properties() const override;
16921692
QgsPointPatternFillSymbolLayer *clone() const override SIP_FACTORY;
16931693
void toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const override;
1694-
QImage toTiledPattern( ) const override;
1694+
QImage toTiledPatternImage( ) const override;
16951695
double estimateMaxBleed( const QgsRenderContext &context ) const override;
16961696
bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override;
16971697
QgsSymbol *subSymbol() override;

‎src/core/symbology/qgssymbollayer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ void QgsFillSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSize
844844
stopRender( context );
845845
}
846846

847-
QImage QgsFillSymbolLayer::toTiledPattern() const
847+
QImage QgsFillSymbolLayer::toTiledPatternImage() const
848848
{
849849
return QImage();
850850
}

‎src/core/symbology/qgssymbollayer.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,13 +1242,16 @@ class CORE_EXPORT QgsFillSymbolLayer : public QgsSymbolLayer
12421242
double angle() const { return mAngle; }
12431243

12441244
/**
1245-
* Renders the symbol layer to an image that can be used as a seamless pattern fill.
1245+
* Renders the symbol layer to an image that can be used as a seamless pattern fill
1246+
* for polygons, this method is used by SLD export to generate image tiles for
1247+
* ExternalGraphic polygon fills.
1248+
*
12461249
* The default implementation returns a null image.
12471250
*
12481251
* \return the tile image (not necessarily a square) or a null image if not implemented.
12491252
* \since QGIS 3.30
12501253
*/
1251-
virtual QImage toTiledPattern( ) const;
1254+
virtual QImage toTiledPatternImage( ) const;
12521255

12531256
protected:
12541257
QgsFillSymbolLayer( bool locked = false );

‎tests/src/python/test_qgssymbollayer_createsld.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
__copyright__ = '(C) 2012, Andrea Aime'
2121

2222
import qgis # NOQA
23-
24-
from qgis.PyQt.QtCore import Qt, QDir, QFile, QIODevice, QPointF, QSizeF
23+
import os
24+
from qgis.PyQt.QtCore import Qt, QDir, QFile, QIODevice, QPointF, QSizeF, QTemporaryDir
2525
from qgis.PyQt.QtXml import QDomDocument
26-
from qgis.PyQt.QtGui import QColor, QFont
26+
from qgis.PyQt.QtGui import QColor, QFont, QImage
2727

2828
from qgis.core import (
2929
Qgis,
@@ -32,7 +32,7 @@
3232
QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsSimpleFillSymbolLayer, QgsSVGFillSymbolLayer,
3333
QgsLinePatternFillSymbolLayer, QgsPointPatternFillSymbolLayer, QgsVectorLayer, QgsVectorLayerSimpleLabeling,
3434
QgsTextBufferSettings, QgsPalLayerSettings, QgsTextBackgroundSettings, QgsRuleBasedLabeling,
35-
QgsLineSymbol, QgsSymbolLayer, QgsSimpleMarkerSymbolLayer, QgsProperty)
35+
QgsLineSymbol, QgsSymbolLayer, QgsSimpleMarkerSymbolLayer, QgsProperty, QgsSldExportContext)
3636
from qgis.testing import start_app, unittest
3737
from utilities import unitTestDataPath
3838

@@ -1237,6 +1237,64 @@ def testDataDefinedAngle(self):
12371237
self.assertEqual(rot.tagName(), 'ogc:PropertyName')
12381238
self.assertEqual(rot.text(), 'field_a')
12391239

1240+
def testSaveSldStyleV2Png(self):
1241+
"""Test SLD export for polygons with PNG tiles"""
1242+
1243+
# Point pattern
1244+
layer = QgsVectorLayer("Polygon", "addfeat", "memory")
1245+
error, ok = layer.loadSldStyle(os.path.join(unitTestDataPath('symbol_layer'), 'QgsPointPatternFillSymbolLayer.sld'))
1246+
self.assertTrue(ok)
1247+
temp_dir = QTemporaryDir()
1248+
temp_path = temp_dir.path()
1249+
sld_path = os.path.join(temp_path, 'export.sld')
1250+
context = QgsSldExportContext(Qgis.SldExportOption.Png, Qgis.SldExportVendorExtension.NoVendorExtension, sld_path)
1251+
message, ok = layer.saveSldStyleV2(context)
1252+
self.assertTrue(ok)
1253+
self.assertTrue(os.path.exists(os.path.join(temp_path, 'export.png')))
1254+
1255+
with open(sld_path, 'r') as f:
1256+
self.assertIn('export.png', f.read())
1257+
1258+
image = QImage(os.path.join(temp_path, 'export.png'))
1259+
self.assertTrue(image.hasAlphaChannel())
1260+
self.assertEqual(image.height(), 33)
1261+
self.assertEqual(image.width(), 33)
1262+
1263+
for x, y in ((16, 0), (0, 16), (16, 16), (32, 16), (16, 32)):
1264+
self.assertEqual(image.pixelColor(x, y).name(), '#000000')
1265+
self.assertEqual(image.pixelColor(x, y).alpha(), 0)
1266+
1267+
for x, y in ((0, 0), (0, 32), (32, 0), (32, 32)):
1268+
self.assertNotEqual(image.pixelColor(x, y).name(), '#000000')
1269+
self.assertGreater(image.pixelColor(x, y).alpha(), 0)
1270+
1271+
# Line pattern
1272+
error, ok = layer.loadSldStyle(os.path.join(unitTestDataPath('symbol_layer'), 'QgsLinePatternFillSymbolLayer.sld'))
1273+
self.assertTrue(ok)
1274+
temp_dir = QTemporaryDir()
1275+
temp_path = temp_dir.path()
1276+
sld_path = os.path.join(temp_path, 'export.sld')
1277+
context = QgsSldExportContext(Qgis.SldExportOption.Png, Qgis.SldExportVendorExtension.NoVendorExtension, sld_path)
1278+
message, ok = layer.saveSldStyleV2(context)
1279+
self.assertTrue(ok)
1280+
self.assertTrue(os.path.exists(os.path.join(temp_path, 'export.png')))
1281+
1282+
with open(sld_path, 'r') as f:
1283+
self.assertIn('export.png', f.read())
1284+
1285+
image = QImage(os.path.join(temp_path, 'export.png'))
1286+
self.assertTrue(image.hasAlphaChannel())
1287+
self.assertEqual(image.height(), 7)
1288+
self.assertEqual(image.width(), 4)
1289+
1290+
for x, y in ((0, 0), (3, 3), (3, 3), (0, 6)):
1291+
self.assertEqual(image.pixelColor(x, y).name(), '#000000')
1292+
self.assertEqual(image.pixelColor(x, y).alpha(), 0)
1293+
1294+
for x, y in ((2, 0), (0, 3), (3, 6)):
1295+
self.assertNotEqual(image.pixelColor(x, y).name(), '#000000')
1296+
self.assertGreater(image.pixelColor(x, y).alpha(), 0)
1297+
12401298
def assertScaleDenominator(self, root, expectedMinScale, expectedMaxScale, index=0):
12411299
rule = root.elementsByTagName('se:Rule').item(index).toElement()
12421300

0 commit comments

Comments
 (0)
Please sign in to comment.