Skip to content

Commit

Permalink
Fix rendering offset lines as part of fill symbol (outside of
Browse files Browse the repository at this point in the history
vector layers) results in broken offset outline
  • Loading branch information
nyalldawson committed Jan 30, 2017
1 parent f80b92a commit 10c40dc
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 2 deletions.
3 changes: 3 additions & 0 deletions python/core/symbology-ng/qgssymbol.sip
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/core/symbology-ng/qgslinesymbollayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 )
{
Expand Down
2 changes: 2 additions & 0 deletions src/core/symbology-ng/qgssymbol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() );

Expand Down Expand Up @@ -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() );

Expand Down
18 changes: 18 additions & 0 deletions src/core/symbology-ng/qgssymbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -494,6 +511,7 @@ class CORE_EXPORT QgsSymbolRenderContext
QgsFields mFields;
int mGeometryPartCount;
int mGeometryPartNum;
QgsWkbTypes::GeometryType mOriginalGeometryType = QgsWkbTypes::UnknownGeometry;


QgsSymbolRenderContext( const QgsSymbolRenderContext& rh );
Expand Down
2 changes: 2 additions & 0 deletions tests/src/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
94 changes: 94 additions & 0 deletions tests/src/python/test_qgsfillsymbollayers.py
Original file line number Diff line number Diff line change
@@ -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()
94 changes: 94 additions & 0 deletions tests/src/python/test_qgslinesymbollayers.py
Original file line number Diff line number Diff line change
@@ -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()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 10c40dc

Please sign in to comment.