Fix rendering offset lines as part of fill symbol (outside of

vector layers) results in broken offset outline
This commit is contained in:
Nyall Dawson 2017-01-30 19:58:08 +10:00
parent f80b92a23f
commit 10c40dcaab
9 changed files with 215 additions and 2 deletions

View File

@ -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)

View File

@ -328,7 +328,7 @@ void QgsSimpleLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo
else
{
double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry );
QList<QPolygonF> 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<QPolygonF> mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry );
QList<QPolygonF> 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 )
{

View File

@ -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<QPolygonF>* 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() );

View File

@ -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 );

View File

@ -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)

View File

@ -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 = "<h1>Python QgsFillSymbolLayer Tests</h1>\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 += "<h2>Render {}</h2>\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()

View File

@ -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 = "<h1>Python QgsLineSymbolLayer Tests</h1>\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 += "<h2>Render {}</h2>\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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B