diff --git a/python/core/symbology-ng/qgssymbol.sip b/python/core/symbology-ng/qgssymbol.sip index 909ea25875c..8fd32a555a8 100644 --- a/python/core/symbology-ng/qgssymbol.sip +++ b/python/core/symbology-ng/qgssymbol.sip @@ -357,6 +357,9 @@ class QgsSymbolRenderContext //! Current feature being rendered - may be null const QgsFeature* feature() const; + void setOriginalGeometryType( QgsWkbTypes::GeometryType type ); + QgsWkbTypes::GeometryType originalGeometryType() const; + //! Fields of the layer. Currently only available in startRender() calls //! to allow symbols with data-defined properties prepare the expressions //! (other times fields() returns null) diff --git a/src/core/symbology-ng/qgslinesymbollayer.cpp b/src/core/symbology-ng/qgslinesymbollayer.cpp index 52aa5481236..eb1a8877899 100644 --- a/src/core/symbology-ng/qgslinesymbollayer.cpp +++ b/src/core/symbology-ng/qgslinesymbollayer.cpp @@ -328,7 +328,7 @@ void QgsSimpleLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo else { double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ); - QList mline = ::offsetLine( points, scaledOffset, context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry ); + QList mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry ); for ( int part = 0; part < mline.count(); ++part ) { #if 0 @@ -876,7 +876,7 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo else { context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset - QList mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry ); + QList mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry ); for ( int part = 0; part < mline.count(); ++part ) { diff --git a/src/core/symbology-ng/qgssymbol.cpp b/src/core/symbology-ng/qgssymbol.cpp index 0a6983d24be..6162c07caf3 100644 --- a/src/core/symbology-ng/qgssymbol.cpp +++ b/src/core/symbology-ng/qgssymbol.cpp @@ -1629,6 +1629,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f //save old painter QPainter* renderPainter = context.painter(); QgsSymbolRenderContext symbolContext( context, outputUnit(), mAlpha, selected, mRenderHints, f, QgsFields(), mapUnitScale() ); + symbolContext.setOriginalGeometryType( QgsWkbTypes::LineGeometry ); symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() ); symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() ); @@ -1709,6 +1710,7 @@ QgsFillSymbol::QgsFillSymbol( const QgsSymbolLayerList& layers ) void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList* rings, const QgsFeature* f, QgsRenderContext& context, int layerIdx, bool selected ) { QgsSymbolRenderContext symbolContext( context, outputUnit(), mAlpha, selected, mRenderHints, f, QgsFields(), mapUnitScale() ); + symbolContext.setOriginalGeometryType( QgsWkbTypes::PolygonGeometry ); symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() ); symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() ); diff --git a/src/core/symbology-ng/qgssymbol.h b/src/core/symbology-ng/qgssymbol.h index 6d7dcd80449..f939d457c36 100644 --- a/src/core/symbology-ng/qgssymbol.h +++ b/src/core/symbology-ng/qgssymbol.h @@ -434,6 +434,23 @@ class CORE_EXPORT QgsSymbolRenderContext //! Current feature being rendered - may be null const QgsFeature* feature() const { return mFeature; } + /** + * Sets the geometry type for the original feature geometry being rendered. + * @see originalGeometryType() + * @note added in QGIS 3.0 + */ + void setOriginalGeometryType( QgsWkbTypes::GeometryType type ) { mOriginalGeometryType = type; } + + /** + * Returns the geometry type for the original feature geometry being rendered. This can be + * useful if symbol layers alter their appearance based on geometry type - eg offsetting a + * simple line style will look different if the simple line is rendering a polygon feature + * (a closed buffer) vs a line feature (an unclosed offset line). + * @see originalGeometryType() + * @note added in QGIS 3.0 + */ + QgsWkbTypes::GeometryType originalGeometryType() const { return mOriginalGeometryType; } + //! Fields of the layer. Currently only available in startRender() calls //! to allow symbols with data-defined properties prepare the expressions //! (other times fields() returns null) @@ -494,6 +511,7 @@ class CORE_EXPORT QgsSymbolRenderContext QgsFields mFields; int mGeometryPartCount; int mGeometryPartNum; + QgsWkbTypes::GeometryType mOriginalGeometryType = QgsWkbTypes::UnknownGeometry; QgsSymbolRenderContext( const QgsSymbolRenderContext& rh ); diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 3987a472df2..67e46c6a98b 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -47,6 +47,7 @@ ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py) ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py) ADD_PYTHON_TEST(PyQgsFeature test_qgsfeature.py) ADD_PYTHON_TEST(PyQgsFieldFormattersTest test_qgsfieldformatters.py) +ADD_PYTHON_TEST(PyQgsFillSymbolLayers test_qgsfillsymbollayers.py) ADD_PYTHON_TEST(PyQgsProject test_qgsproject.py) ADD_PYTHON_TEST(PyQgsFeatureIterator test_qgsfeatureiterator.py) ADD_PYTHON_TEST(PyQgsField test_qgsfield.py) @@ -61,6 +62,7 @@ ADD_PYTHON_TEST(PyQgsGeometryValidator test_qgsgeometryvalidator.py) ADD_PYTHON_TEST(PyQgsGraduatedSymbolRenderer test_qgsgraduatedsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsInterval test_qgsinterval.py) ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py) +ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py) ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py) ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py) ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py) diff --git a/tests/src/python/test_qgsfillsymbollayers.py b/tests/src/python/test_qgsfillsymbollayers.py new file mode 100644 index 00000000000..6024b0de959 --- /dev/null +++ b/tests/src/python/test_qgsfillsymbollayers.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsFillSymbolLayers. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '2017-01' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + + +import qgis # NOQA + +from qgis.testing import unittest +from qgis.PyQt.QtCore import QDir +from qgis.PyQt.QtGui import (QImage, + QPainter, + QColor) +from qgis.core import (QgsRenderChecker, + QgsSimpleLineSymbolLayer, + QgsMapSettings, + QgsFillSymbol, + QgsGeometry, + QgsFeature, + QgsRenderContext) + + +class TestQgsFillSymbolLayers(unittest.TestCase): + + def setUp(self): + self.report = "

Python QgsFillSymbolLayer Tests

\n" + + def tearDown(self): + report_file_path = "%s/qgistest.html" % QDir.tempPath() + with open(report_file_path, 'a') as report_file: + report_file.write(self.report) + + def imageCheck(self, name, reference_image, image): + self.report += "

Render {}

\n".format(name) + temp_dir = QDir.tempPath() + '/' + file_name = temp_dir + 'symbollayer_' + name + ".png" + image.save(file_name, "PNG") + checker = QgsRenderChecker() + checker.setControlPathPrefix("symbol_layer") + checker.setControlName("expected_" + reference_image) + checker.setRenderedImage(file_name) + checker.setColorTolerance(2) + result = checker.compareImages(name, 0) + self.report += checker.report() + print((self.report)) + return result + + def testSimpleLineWithOffset(self): + """ test that rendering a polygon with simple line symbol with offset results in closed line""" + layer = QgsSimpleLineSymbolLayer() + layer.setOffset(-1) + + symbol = QgsFillSymbol() + symbol.changeSymbolLayer(0, layer) + + image = QImage(200, 200, QImage.Format_RGB32) + painter = QPainter() + ms = QgsMapSettings() + + geom = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0))') + f = QgsFeature() + f.setGeometry(geom) + + extent = geom.geometry().boundingBox() + # buffer extent by 10% + extent = extent.buffer((extent.height() + extent.width()) / 20.0) + + ms.setExtent(extent) + ms.setOutputSize(image.size()) + context = QgsRenderContext.fromMapSettings(ms) + context.setPainter(painter) + context.setScaleFactor(96 / 25.4) # 96 DPI + + painter.begin(image) + image.fill(QColor(255, 255, 255)) + + symbol.startRender(context) + symbol.renderFeature(f, context) + symbol.stopRender(context) + painter.end() + + self.assertTrue(self.imageCheck('symbol_layer', 'fill_simpleline_offset', image)) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgslinesymbollayers.py b/tests/src/python/test_qgslinesymbollayers.py new file mode 100644 index 00000000000..7a3484a1cf2 --- /dev/null +++ b/tests/src/python/test_qgslinesymbollayers.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsLineSymbolLayers. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '2017-01' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + + +import qgis # NOQA + +from qgis.testing import unittest +from qgis.PyQt.QtCore import QDir +from qgis.PyQt.QtGui import (QImage, + QPainter, + QColor) +from qgis.core import (QgsRenderChecker, + QgsSimpleLineSymbolLayer, + QgsMapSettings, + QgsLineSymbol, + QgsGeometry, + QgsFeature, + QgsRenderContext) + + +class TestQgsLineSymbolLayers(unittest.TestCase): + + def setUp(self): + self.report = "

Python QgsLineSymbolLayer Tests

\n" + + def tearDown(self): + report_file_path = "%s/qgistest.html" % QDir.tempPath() + with open(report_file_path, 'a') as report_file: + report_file.write(self.report) + + def imageCheck(self, name, reference_image, image): + self.report += "

Render {}

\n".format(name) + temp_dir = QDir.tempPath() + '/' + file_name = temp_dir + 'symbollayer_' + name + ".png" + image.save(file_name, "PNG") + checker = QgsRenderChecker() + checker.setControlPathPrefix("symbol_layer") + checker.setControlName("expected_" + reference_image) + checker.setRenderedImage(file_name) + checker.setColorTolerance(2) + result = checker.compareImages(name, 0) + self.report += checker.report() + print((self.report)) + return result + + def testSimpleLineWithOffset(self): + """ test that rendering a simple line symbol with offset""" + layer = QgsSimpleLineSymbolLayer() + layer.setOffset(1) + + symbol = QgsLineSymbol() + symbol.changeSymbolLayer(0, layer) + + image = QImage(200, 200, QImage.Format_RGB32) + painter = QPainter() + ms = QgsMapSettings() + + geom = QgsGeometry.fromWkt('LineString (0 0, 10 0, 10 10, 0 10, 0 0)') + f = QgsFeature() + f.setGeometry(geom) + + extent = geom.geometry().boundingBox() + # buffer extent by 10% + extent = extent.buffer((extent.height() + extent.width()) / 20.0) + + ms.setExtent(extent) + ms.setOutputSize(image.size()) + context = QgsRenderContext.fromMapSettings(ms) + context.setPainter(painter) + context.setScaleFactor(96 / 25.4) # 96 DPI + + painter.begin(image) + image.fill(QColor(255, 255, 255)) + + symbol.startRender(context) + symbol.renderFeature(f, context) + symbol.stopRender(context) + painter.end() + + self.assertTrue(self.imageCheck('symbol_layer', 'simpleline_offset', image)) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testdata/control_images/symbol_layer/expected_fill_simpleline_offset/expected_fill_simpleline_offset.png b/tests/testdata/control_images/symbol_layer/expected_fill_simpleline_offset/expected_fill_simpleline_offset.png new file mode 100644 index 00000000000..2d5d2b7993a Binary files /dev/null and b/tests/testdata/control_images/symbol_layer/expected_fill_simpleline_offset/expected_fill_simpleline_offset.png differ diff --git a/tests/testdata/control_images/symbol_layer/expected_simpleline_offset/expected_simpleline_offset.png b/tests/testdata/control_images/symbol_layer/expected_simpleline_offset/expected_simpleline_offset.png new file mode 100644 index 00000000000..d3b92262754 Binary files /dev/null and b/tests/testdata/control_images/symbol_layer/expected_simpleline_offset/expected_simpleline_offset.png differ