From d2e68cf6471433b90345e0720523659254a9cb70 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 4 Oct 2024 10:40:41 +1000 Subject: [PATCH] Non-default blend mode should force raster render of layer Fixes rendering of vector tile and annotation layers with non default blend modes in layouts Fixes #55629 (cherry picked from commit 9ed009fefc7d992fb055729717338646c0782cba) --- .../qgsannotationlayerrenderer.cpp | 12 +++++++++- .../annotations/qgsannotationlayerrenderer.h | 1 + .../vectortile/qgsvectortilelayerrenderer.cpp | 12 +++++++++- .../vectortile/qgsvectortilelayerrenderer.h | 2 ++ tests/src/python/test_qgsannotationlayer.py | 19 ++++++++++++++++ tests/src/python/test_qgsvectortile.py | 22 +++++++++++++++++++ 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/core/annotations/qgsannotationlayerrenderer.cpp b/src/core/annotations/qgsannotationlayerrenderer.cpp index 3b4e8bbdad89..545358948b08 100644 --- a/src/core/annotations/qgsannotationlayerrenderer.cpp +++ b/src/core/annotations/qgsannotationlayerrenderer.cpp @@ -26,6 +26,7 @@ QgsAnnotationLayerRenderer::QgsAnnotationLayerRenderer( QgsAnnotationLayer *laye : QgsMapLayerRenderer( layer->id(), &context ) , mFeedback( std::make_unique< QgsFeedback >() ) , mLayerOpacity( layer->opacity() ) + , mLayerBlendMode( layer->blendMode() ) { // Clone items from layer which fall inside the rendered extent // Because some items have scale dependent bounds, we have to accept some limitations here. @@ -111,5 +112,14 @@ bool QgsAnnotationLayerRenderer::render() bool QgsAnnotationLayerRenderer::forceRasterRender() const { - return renderContext()->testFlag( Qgis::RenderContextFlag::UseAdvancedEffects ) && ( !qgsDoubleNear( mLayerOpacity, 1.0 ) ); + if ( !renderContext()->testFlag( Qgis::RenderContextFlag::UseAdvancedEffects ) ) + return false; + + if ( !qgsDoubleNear( mLayerOpacity, 1.0 ) ) + return true; + + if ( mLayerBlendMode != QPainter::CompositionMode_SourceOver ) + return true; + + return false; } diff --git a/src/core/annotations/qgsannotationlayerrenderer.h b/src/core/annotations/qgsannotationlayerrenderer.h index d4765b677179..5d2aa2a86e95 100644 --- a/src/core/annotations/qgsannotationlayerrenderer.h +++ b/src/core/annotations/qgsannotationlayerrenderer.h @@ -54,6 +54,7 @@ class CORE_EXPORT QgsAnnotationLayerRenderer : public QgsMapLayerRenderer std::vector < std::pair< QString, std::unique_ptr< QgsAnnotationItem > > > mItems; std::unique_ptr< QgsFeedback > mFeedback; double mLayerOpacity = 1.0; + QPainter::CompositionMode mLayerBlendMode = QPainter::CompositionMode::CompositionMode_SourceOver; std::unique_ptr< QgsPaintEffect > mPaintEffect; }; diff --git a/src/core/vectortile/qgsvectortilelayerrenderer.cpp b/src/core/vectortile/qgsvectortilelayerrenderer.cpp index 708671ed7437..3e854dd931e5 100644 --- a/src/core/vectortile/qgsvectortilelayerrenderer.cpp +++ b/src/core/vectortile/qgsvectortilelayerrenderer.cpp @@ -38,6 +38,7 @@ QgsVectorTileLayerRenderer::QgsVectorTileLayerRenderer( QgsVectorTileLayer *laye , mLayerName( layer->name() ) , mDataProvider( qgis::down_cast< const QgsVectorTileDataProvider* >( layer->dataProvider() )->clone() ) , mRenderer( layer->renderer()->clone() ) + , mLayerBlendMode( layer->blendMode() ) , mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() ) , mLabelsEnabled( layer->labelsEnabled() ) , mFeedback( new QgsFeedback ) @@ -237,7 +238,16 @@ bool QgsVectorTileLayerRenderer::render() bool QgsVectorTileLayerRenderer::forceRasterRender() const { - return renderContext()->testFlag( Qgis::RenderContextFlag::UseAdvancedEffects ) && ( !qgsDoubleNear( mLayerOpacity, 1.0 ) ); + if ( !renderContext()->testFlag( Qgis::RenderContextFlag::UseAdvancedEffects ) ) + return false; + + if ( !qgsDoubleNear( mLayerOpacity, 1.0 ) ) + return true; + + if ( mLayerBlendMode != QPainter::CompositionMode_SourceOver ) + return true; + + return false; } void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile ) diff --git a/src/core/vectortile/qgsvectortilelayerrenderer.h b/src/core/vectortile/qgsvectortilelayerrenderer.h index 725d3ff17d00..6e7097e070c4 100644 --- a/src/core/vectortile/qgsvectortilelayerrenderer.h +++ b/src/core/vectortile/qgsvectortilelayerrenderer.h @@ -63,6 +63,8 @@ class QgsVectorTileLayerRenderer : public QgsMapLayerRenderer //! Tile renderer object to do rendering of individual tiles std::unique_ptr mRenderer; + QPainter::CompositionMode mLayerBlendMode = QPainter::CompositionMode::CompositionMode_SourceOver; + /** * Label provider that handles registration of labels. * No need to delete: if exists it is owned by labeling engine. diff --git a/tests/src/python/test_qgsannotationlayer.py b/tests/src/python/test_qgsannotationlayer.py index f1e1ee8f4962..cb12e8b11bca 100644 --- a/tests/src/python/test_qgsannotationlayer.py +++ b/tests/src/python/test_qgsannotationlayer.py @@ -598,6 +598,25 @@ def test_render_via_job_with_transform(self): self.assertTrue(compareWkt(result, expected, tol=1000), "mismatch Expected:\n{}\nGot:\n{}\n".format(expected, result)) + def test_force_raster_render(self): + layer = QgsAnnotationLayer('test', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext())) + self.assertTrue(layer.isValid()) + settings = QgsMapSettings() + rc = QgsRenderContext.fromMapSettings(settings) + renderer = layer.createMapRenderer(rc) + self.assertFalse(renderer.forceRasterRender()) + + # layer opacity should force raster render + layer.setOpacity(0.5) + renderer = layer.createMapRenderer(rc) + self.assertTrue(renderer.forceRasterRender()) + layer.setOpacity(1.0) + + # alternate blend mode should force raster render + layer.setBlendMode(QPainter.CompositionMode.CompositionMode_Multiply) + renderer = layer.createMapRenderer(rc) + self.assertTrue(renderer.forceRasterRender()) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgsvectortile.py b/tests/src/python/test_qgsvectortile.py index c38e1cc2eb6f..c76729f91362 100644 --- a/tests/src/python/test_qgsvectortile.py +++ b/tests/src/python/test_qgsvectortile.py @@ -20,6 +20,7 @@ import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy +from qgis.PyQt.QtGui import QPainter from qgis.core import ( Qgis, QgsDataSourceUri, @@ -30,6 +31,8 @@ QgsVectorLayer, QgsVectorTileLayer, QgsVectorTileWriter, + QgsMapSettings, + QgsRenderContext ) import unittest from qgis.testing import start_app, QgisTestCase @@ -295,6 +298,25 @@ def testSelection(self): self.assertCountEqual({f.geometry().asWkt(-3) for f in layer.selectedFeatures()}, {'Polygon ((-10958000 3835000, -10958000 3796000, -10919000 3835000, -10841000 3874000, -10684000 3992000, -10567000 4031000, -10410000 4031000, -10332000 3992000, -10254000 3914000, -10136000 3914000, -10058000 3874000, -10019000 3796000, -10019000 3757000, -10058000 3718000, -10097000 3718000, -10254000 3796000, -10332000 3796000, -10371000 3757000, -10371000 3718000, -10371000 3679000, -10332000 3640000, -10332000 3561000, -10410000 3483000, -10449000 3405000, -10488000 3366000, -10645000 3327000, -10723000 3366000, -10762000 3405000, -10801000 3444000, -10762000 3483000, -10801000 3522000, -10841000 3561000, -10919000 3561000, -10958000 3600000, -10958000 3640000, -10958000 3679000, -10958000 3718000, -10997000 3796000, -10958000 3835000))'}) self.assertEqual(len(spy), 11) + def test_force_raster_render(self): + layer = QgsVectorTileLayer(f"type=vtpk&url={unitTestDataPath() + '/testvtpk.vtpk'}", 'tiles') + self.assertTrue(layer.isValid()) + settings = QgsMapSettings() + rc = QgsRenderContext.fromMapSettings(settings) + renderer = layer.createMapRenderer(rc) + self.assertFalse(renderer.forceRasterRender()) + + # layer opacity should force raster render + layer.setOpacity(0.5) + renderer = layer.createMapRenderer(rc) + self.assertTrue(renderer.forceRasterRender()) + layer.setOpacity(1.0) + + # alternate blend mode should force raster render + layer.setBlendMode(QPainter.CompositionMode.CompositionMode_Multiply) + renderer = layer.createMapRenderer(rc) + self.assertTrue(renderer.forceRasterRender()) + if __name__ == '__main__': unittest.main()