Skip to content

Commit 50cbb29

Browse files
committedDec 2, 2018
[FEATURE] Add option to force right-hand-rule during polygon symbol rendering
This new option, available under the "Advanced" button for fill symbols, allows forcing rendered polygons to follow the standard "right hand rule" for ring orientation (where exterior ring is clockwise, and interior rings are all counter-clockwise). The orientation fix is applied while rendering only, and the original feature geometry is unchanged. This allows for creation of fill symbols with consistent appearance, regardless of the dataset being rendered and the ring orientation of individual features. Refs #12652 (cherry picked from commit 8269031)
1 parent 854228c commit 50cbb29

File tree

9 files changed

+223
-19
lines changed

9 files changed

+223
-19
lines changed
 

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

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,32 @@ side effects for certain symbol types.
370370
.. seealso:: :py:func:`setClipFeaturesToExtent`
371371

372372
.. versionadded:: 2.9
373+
%End
374+
375+
void setForceRHR( bool force );
376+
%Docstring
377+
Sets whether polygon features drawn by the symbol should be reoriented to follow the
378+
standard right-hand-rule orientation, in which the area that is
379+
bounded by the polygon is to the right of the boundary. In particular, the exterior
380+
ring is oriented in a clockwise direction and the interior rings in a counter-clockwise
381+
direction.
382+
383+
.. seealso:: :py:func:`forceRHR`
384+
385+
.. versionadded:: 3.6
386+
%End
387+
388+
bool forceRHR() const;
389+
%Docstring
390+
Returns true if polygon features drawn by the symbol will be reoriented to follow the
391+
standard right-hand-rule orientation, in which the area that is
392+
bounded by the polygon is to the right of the boundary. In particular, the exterior
393+
ring is oriented in a clockwise direction and the interior rings in a counter-clockwise
394+
direction.
395+
396+
.. seealso:: :py:func:`setForceRHR`
397+
398+
.. versionadded:: 3.6
373399
%End
374400

375401
QSet<QString> usedAttributes( const QgsRenderContext &context ) const;
@@ -428,14 +454,20 @@ Creates a point in screen coordinates from a QgsPoint in map coordinates
428454
Creates a line string in screen coordinates from a QgsCurve in map coordinates
429455
%End
430456

431-
static QPolygonF _getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent );
457+
static QPolygonF _getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent, bool isExteriorRing = false, bool correctRingOrientation = false );
432458
%Docstring
433-
Creates a polygon ring in screen coordinates from a QgsCurve in map coordinates
459+
Creates a polygon ring in screen coordinates from a QgsCurve in map coordinates.
460+
461+
If ``correctRingOrientation`` is true then the ring will be oriented to match standard ring orientation, e.g.
462+
clockwise for exterior rings and counter-clockwise for interior rings.
434463
%End
435464

436-
static void _getPolygon( QPolygonF &pts, QList<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent = true );
465+
static void _getPolygon( QPolygonF &pts, QList<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent = true, bool correctRingOrientation = false );
437466
%Docstring
438467
Creates a polygon in screen coordinates from a QgsPolygonXYin map coordinates
468+
469+
If ``correctRingOrientation`` is true then the ring will be oriented to match standard ring orientation, e.g.
470+
clockwise for exterior rings and counter-clockwise for interior rings.
439471
%End
440472

441473
QgsSymbolLayerList cloneLayers() const /Factory/;

‎src/core/symbology/qgssymbol.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &
141141
return pts;
142142
}
143143

144-
QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
144+
QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
145145
{
146146
const QgsCoordinateTransform ct = context.coordinateTransform();
147147
const QgsMapToPixel &mtp = context.mapToPixel();
@@ -155,6 +155,15 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
155155
if ( curve.numPoints() < 1 )
156156
return QPolygonF();
157157

158+
if ( correctRingOrientation )
159+
{
160+
// ensure consistent polygon ring orientation
161+
if ( isExteriorRing && curve.orientation() != QgsCurve::Clockwise )
162+
std::reverse( poly.begin(), poly.end() );
163+
else if ( !isExteriorRing && curve.orientation() != QgsCurve::CounterClockwise )
164+
std::reverse( poly.begin(), poly.end() );
165+
}
166+
158167
//clip close to view extent, if needed
159168
const QRectF ptsRect = poly.boundingRect();
160169
if ( clipToExtent && !context.extent().contains( ptsRect ) )
@@ -184,14 +193,14 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
184193
return poly;
185194
}
186195

187-
void QgsSymbol::_getPolygon( QPolygonF &pts, QList<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent )
196+
void QgsSymbol::_getPolygon( QPolygonF &pts, QList<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, const bool clipToExtent, const bool correctRingOrientation )
188197
{
189198
holes.clear();
190199

191-
pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent );
200+
pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent, true, correctRingOrientation );
192201
for ( int idx = 0; idx < polygon.numInteriorRings(); idx++ )
193202
{
194-
const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent );
203+
const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent, false, correctRingOrientation );
195204
if ( !hole.isEmpty() ) holes.append( hole );
196205
}
197206
}
@@ -850,7 +859,7 @@ void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &cont
850859
QgsDebugMsg( QStringLiteral( "cannot render polygon with no exterior ring" ) );
851860
break;
852861
}
853-
_getPolygon( pts, holes, context, polygon, !tileMapRendering && clipFeaturesToExtent() );
862+
_getPolygon( pts, holes, context, polygon, !tileMapRendering && clipFeaturesToExtent(), mForceRHR );
854863
static_cast<QgsFillSymbol *>( this )->renderPolygon( pts, ( !holes.isEmpty() ? &holes : nullptr ), &feature, context, layer, selected );
855864

856865
if ( drawVertexMarker && !usingSegmentizedGeometry )
@@ -980,7 +989,7 @@ void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &cont
980989
if ( !polygon.exteriorRing() )
981990
break;
982991

983-
_getPolygon( pts, holes, context, polygon, !tileMapRendering && clipFeaturesToExtent() );
992+
_getPolygon( pts, holes, context, polygon, !tileMapRendering && clipFeaturesToExtent(), mForceRHR );
984993
static_cast<QgsFillSymbol *>( this )->renderPolygon( pts, ( !holes.isEmpty() ? &holes : nullptr ), &feature, context, layer, selected );
985994

986995
if ( drawVertexMarker && !usingSegmentizedGeometry )
@@ -1574,6 +1583,7 @@ QgsMarkerSymbol *QgsMarkerSymbol::clone() const
15741583
cloneSymbol->setLayer( mLayer );
15751584
Q_NOWARN_DEPRECATED_POP
15761585
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
1586+
cloneSymbol->setForceRHR( mForceRHR );
15771587
return cloneSymbol;
15781588
}
15791589

@@ -1793,6 +1803,7 @@ QgsLineSymbol *QgsLineSymbol::clone() const
17931803
cloneSymbol->setLayer( mLayer );
17941804
Q_NOWARN_DEPRECATED_POP
17951805
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
1806+
cloneSymbol->setForceRHR( mForceRHR );
17961807
return cloneSymbol;
17971808
}
17981809

@@ -1913,6 +1924,7 @@ QgsFillSymbol *QgsFillSymbol::clone() const
19131924
cloneSymbol->setLayer( mLayer );
19141925
Q_NOWARN_DEPRECATED_POP
19151926
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
1927+
cloneSymbol->setForceRHR( mForceRHR );
19161928
return cloneSymbol;
19171929
}
19181930

‎src/core/symbology/qgssymbol.h

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,28 @@ class CORE_EXPORT QgsSymbol
379379
*/
380380
bool clipFeaturesToExtent() const { return mClipFeaturesToExtent; }
381381

382+
/**
383+
* Sets whether polygon features drawn by the symbol should be reoriented to follow the
384+
* standard right-hand-rule orientation, in which the area that is
385+
* bounded by the polygon is to the right of the boundary. In particular, the exterior
386+
* ring is oriented in a clockwise direction and the interior rings in a counter-clockwise
387+
* direction.
388+
* \see forceRHR()
389+
* \since QGIS 3.6
390+
*/
391+
void setForceRHR( bool force ) { mForceRHR = force; }
392+
393+
/**
394+
* Returns true if polygon features drawn by the symbol will be reoriented to follow the
395+
* standard right-hand-rule orientation, in which the area that is
396+
* bounded by the polygon is to the right of the boundary. In particular, the exterior
397+
* ring is oriented in a clockwise direction and the interior rings in a counter-clockwise
398+
* direction.
399+
* \see setForceRHR()
400+
* \since QGIS 3.6
401+
*/
402+
bool forceRHR() const { return mForceRHR; }
403+
382404
/**
383405
* Returns a list of attributes required to render this feature.
384406
* This should include any attributes required by the symbology including
@@ -447,14 +469,21 @@ class CORE_EXPORT QgsSymbol
447469
static QPolygonF _getLineString( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent = true );
448470

449471
/**
450-
* Creates a polygon ring in screen coordinates from a QgsCurve in map coordinates
472+
* Creates a polygon ring in screen coordinates from a QgsCurve in map coordinates.
473+
*
474+
* If \a correctRingOrientation is true then the ring will be oriented to match standard ring orientation, e.g.
475+
* clockwise for exterior rings and counter-clockwise for interior rings.
451476
*/
452-
static QPolygonF _getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent );
477+
static QPolygonF _getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent, bool isExteriorRing = false, bool correctRingOrientation = false );
453478

454479
/**
455480
* Creates a polygon in screen coordinates from a QgsPolygonXYin map coordinates
481+
*
482+
* If \a correctRingOrientation is true then the ring will be oriented to match standard ring orientation, e.g.
483+
* clockwise for exterior rings and counter-clockwise for interior rings.
484+
*
456485
*/
457-
static void _getPolygon( QPolygonF &pts, QList<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent = true );
486+
static void _getPolygon( QPolygonF &pts, QList<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent = true, bool correctRingOrientation = false );
458487

459488
/**
460489
* Retrieve a cloned list of all layers that make up this symbol.
@@ -487,6 +516,7 @@ class CORE_EXPORT QgsSymbol
487516

488517
RenderHints mRenderHints = nullptr;
489518
bool mClipFeaturesToExtent = true;
519+
bool mForceRHR = false;
490520

491521
Q_DECL_DEPRECATED const QgsVectorLayer *mLayer = nullptr; //current vectorlayer
492522

‎src/core/symbology/qgssymbollayerutils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const Qg
960960
}
961961
symbol->setOpacity( element.attribute( QStringLiteral( "alpha" ), QStringLiteral( "1.0" ) ).toDouble() );
962962
symbol->setClipFeaturesToExtent( element.attribute( QStringLiteral( "clip_to_extent" ), QStringLiteral( "1" ) ).toInt() );
963-
963+
symbol->setForceRHR( element.attribute( QStringLiteral( "force_rhr" ), QStringLiteral( "0" ) ).toInt() );
964964
return symbol;
965965
}
966966

@@ -1031,6 +1031,7 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, QgsSymbol *sym
10311031
symEl.setAttribute( QStringLiteral( "name" ), name );
10321032
symEl.setAttribute( QStringLiteral( "alpha" ), QString::number( symbol->opacity() ) );
10331033
symEl.setAttribute( QStringLiteral( "clip_to_extent" ), symbol->clipFeaturesToExtent() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1034+
symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
10341035
//QgsDebugMsg( "num layers " + QString::number( symbol->symbolLayerCount() ) );
10351036

10361037
for ( int i = 0; i < symbol->symbolLayerCount(); i++ )

‎src/gui/symbology/qgssymbolslistwidget.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbol *symbol, QgsStyle *style,
117117
mClipFeaturesAction = new QAction( tr( "Clip Features to Canvas Extent" ), this );
118118
mClipFeaturesAction->setCheckable( true );
119119
connect( mClipFeaturesAction, &QAction::toggled, this, &QgsSymbolsListWidget::clipFeaturesToggled );
120+
mStandardizeRingsAction = new QAction( tr( "Force Right-Hand-Rule Orientation" ), this );
121+
mStandardizeRingsAction->setCheckable( true );
122+
connect( mStandardizeRingsAction, &QAction::toggled, this, &QgsSymbolsListWidget::forceRHRToggled );
120123

121124
double iconSize = Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 10;
122125
viewSymbols->setIconSize( QSize( static_cast< int >( iconSize ), static_cast< int >( iconSize * 0.9 ) ) ); // ~100, 90 on low dpi
@@ -218,6 +221,7 @@ QgsSymbolsListWidget::~QgsSymbolsListWidget()
218221
// This action was added to the menu by this widget, clean it up
219222
// The menu can be passed in the constructor, so may live longer than this widget
220223
btnAdvanced->menu()->removeAction( mClipFeaturesAction );
224+
btnAdvanced->menu()->removeAction( mStandardizeRingsAction );
221225
}
222226

223227
void QgsSymbolsListWidget::registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsSymbolLayer::Property key )
@@ -394,6 +398,15 @@ void QgsSymbolsListWidget::updateModelFilters()
394398
}
395399
}
396400

401+
void QgsSymbolsListWidget::forceRHRToggled( bool checked )
402+
{
403+
if ( !mSymbol )
404+
return;
405+
406+
mSymbol->setForceRHR( checked );
407+
emit changed();
408+
}
409+
397410
void QgsSymbolsListWidget::openStyleManager()
398411
{
399412
// prefer to use global window manager to open the style manager, if possible!
@@ -687,27 +700,34 @@ void QgsSymbolsListWidget::updateSymbolInfo()
687700

688701
mOpacityWidget->setOpacity( mSymbol->opacity() );
689702

690-
// Remove all previous clip actions
703+
// Clean up previous advanced symbol actions
691704
const QList<QAction *> actionList( btnAdvanced->menu()->actions() );
692705
for ( const auto &action : actionList )
693706
{
694707
if ( mClipFeaturesAction->text() == action->text() )
695708
{
696709
btnAdvanced->menu()->removeAction( action );
697710
}
711+
else if ( mStandardizeRingsAction->text() == action->text() )
712+
{
713+
btnAdvanced->menu()->removeAction( action );
714+
}
698715
}
699716

700717
if ( mSymbol->type() == QgsSymbol::Line || mSymbol->type() == QgsSymbol::Fill )
701718
{
702719
//add clip features option for line or fill symbols
703720
btnAdvanced->menu()->addAction( mClipFeaturesAction );
704721
}
722+
if ( mSymbol->type() == QgsSymbol::Fill )
723+
{
724+
btnAdvanced->menu()->addAction( mStandardizeRingsAction );
725+
}
705726

706727
btnAdvanced->setVisible( mAdvancedMenu || !btnAdvanced->menu()->isEmpty() );
707728

708-
mClipFeaturesAction->blockSignals( true );
709-
mClipFeaturesAction->setChecked( mSymbol->clipFeaturesToExtent() );
710-
mClipFeaturesAction->blockSignals( false );
729+
whileBlocking( mClipFeaturesAction )->setChecked( mSymbol->clipFeaturesToExtent() );
730+
whileBlocking( mStandardizeRingsAction )->setChecked( mSymbol->forceRHR() );
711731
}
712732

713733
void QgsSymbolsListWidget::setSymbolFromStyle( const QModelIndex &index )

‎src/gui/symbology/qgssymbolslistwidget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,15 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW
118118
void opacityChanged( double value );
119119
void createAuxiliaryField();
120120
void updateModelFilters();
121+
void forceRHRToggled( bool checked );
121122

122123
private:
123124
QgsSymbol *mSymbol = nullptr;
124125
std::shared_ptr< QgsSymbol > mAssistantSymbol;
125126
QgsStyle *mStyle = nullptr;
126127
QMenu *mAdvancedMenu = nullptr;
127128
QAction *mClipFeaturesAction = nullptr;
129+
QAction *mStandardizeRingsAction = nullptr;
128130
QgsVectorLayer *mLayer = nullptr;
129131
QgsMapCanvas *mMapCanvas = nullptr;
130132
QgsStyleProxyModel *mModel = nullptr;

‎tests/src/python/test_qgssymbol.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727

2828
from utilities import unitTestDataPath
2929

30-
from qgis.PyQt.QtCore import QDir
30+
from qgis.PyQt.QtCore import QDir, Qt
3131
from qgis.PyQt.QtGui import QImage, QColor, QPainter
32+
from qgis.PyQt.QtXml import QDomDocument
3233

3334
from qgis.core import (QgsGeometry,
3435
QgsRectangle,
@@ -47,9 +48,13 @@
4748
QgsRenderChecker,
4849
QgsSimpleMarkerSymbolLayer,
4950
QgsSimpleMarkerSymbolLayerBase,
51+
QgsSimpleFillSymbolLayer,
5052
QgsUnitTypes,
5153
QgsWkbTypes,
52-
QgsProject
54+
QgsProject,
55+
QgsReadWriteContext,
56+
QgsSymbolLayerUtils,
57+
QgsMarkerLineSymbolLayer
5358
)
5459

5560
from qgis.testing import unittest, start_app
@@ -623,5 +628,107 @@ def testSizeMapUnitScale(self):
623628
self.assertEqual(markerSymbol.symbolLayer(2).sizeMapUnitScale(), QgsMapUnitScale(3000, 4000))
624629

625630

631+
class TestQgsFillSymbol(unittest.TestCase):
632+
633+
def setUp(self):
634+
self.report = "<h1>Python QgsFillSymbol Tests</h1>\n"
635+
636+
def tearDown(self):
637+
report_file_path = "%s/qgistest.html" % QDir.tempPath()
638+
with open(report_file_path, 'a') as report_file:
639+
report_file.write(self.report)
640+
641+
def testForceRHR(self):
642+
# test forcing right hand rule during rendering
643+
644+
s = QgsFillSymbol()
645+
s.deleteSymbolLayer(0)
646+
s.appendSymbolLayer(
647+
QgsSimpleFillSymbolLayer(color=QColor(255, 0, 0), strokeColor=QColor(0, 255, 0)))
648+
self.assertFalse(s.forceRHR())
649+
s.setForceRHR(True)
650+
self.assertTrue(s.forceRHR())
651+
s.setForceRHR(False)
652+
self.assertFalse(s.forceRHR())
653+
654+
s.setForceRHR(True)
655+
doc = QDomDocument()
656+
context = QgsReadWriteContext()
657+
element = QgsSymbolLayerUtils.saveSymbol('test', s, doc, context)
658+
659+
s2 = QgsSymbolLayerUtils.loadSymbol(element, context)
660+
self.assertTrue(s2.forceRHR())
661+
662+
# rendering test
663+
s3 = QgsFillSymbol()
664+
s3.deleteSymbolLayer(0)
665+
s3.appendSymbolLayer(
666+
QgsSimpleFillSymbolLayer(color=QColor(255, 200, 200), strokeColor=QColor(0, 255, 0), strokeWidth=2))
667+
marker_line = QgsMarkerLineSymbolLayer(True)
668+
marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
669+
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
670+
marker.setColor(QColor(255, 0, 0))
671+
marker.setStrokeStyle(Qt.NoPen)
672+
marker_symbol = QgsMarkerSymbol()
673+
marker_symbol.changeSymbolLayer(0, marker)
674+
marker_line.setSubSymbol(marker_symbol)
675+
s3.appendSymbolLayer(marker_line)
676+
677+
g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
678+
rendered_image = self.renderGeometry(s3, g)
679+
assert self.imageCheck('force_rhr_off', 'polygon_forcerhr_off', rendered_image)
680+
681+
s3.setForceRHR(True)
682+
rendered_image = self.renderGeometry(s3, g)
683+
assert self.imageCheck('force_rhr_on', 'polygon_forcerhr_on', rendered_image)
684+
685+
def renderGeometry(self, symbol, geom):
686+
f = QgsFeature()
687+
f.setGeometry(geom)
688+
689+
image = QImage(200, 200, QImage.Format_RGB32)
690+
691+
painter = QPainter()
692+
ms = QgsMapSettings()
693+
extent = geom.get().boundingBox()
694+
# buffer extent by 10%
695+
if extent.width() > 0:
696+
extent = extent.buffered((extent.height() + extent.width()) / 20.0)
697+
else:
698+
extent = extent.buffered(10)
699+
700+
ms.setExtent(extent)
701+
ms.setOutputSize(image.size())
702+
context = QgsRenderContext.fromMapSettings(ms)
703+
context.setPainter(painter)
704+
context.setScaleFactor(96 / 25.4) # 96 DPI
705+
706+
painter.begin(image)
707+
try:
708+
image.fill(QColor(0, 0, 0))
709+
symbol.startRender(context)
710+
symbol.renderFeature(f, context)
711+
symbol.stopRender(context)
712+
finally:
713+
painter.end()
714+
715+
return image
716+
717+
def imageCheck(self, name, reference_image, image):
718+
self.report += "<h2>Render {}</h2>\n".format(name)
719+
temp_dir = QDir.tempPath() + '/'
720+
file_name = temp_dir + 'symbol_' + name + ".png"
721+
image.save(file_name, "PNG")
722+
checker = QgsRenderChecker()
723+
checker.setControlPathPrefix("symbol")
724+
checker.setControlName("expected_" + reference_image)
725+
checker.setRenderedImage(file_name)
726+
checker.setColorTolerance(2)
727+
result = checker.compareImages(name, 20)
728+
self.report += checker.report()
729+
print((self.report))
730+
return result
731+
732+
626733
if __name__ == '__main__':
627734
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.