Fix legend item with opacity forces whole layout to rasterize

and expand test coverage of blend modes and opacity with
legend items
This commit is contained in:
Nyall Dawson 2023-10-10 12:07:49 +10:00
parent d3d1dfde36
commit 3588d41786
9 changed files with 234 additions and 2 deletions

View File

@ -94,6 +94,10 @@ The caller takes responsibility for deleting the returned object.
virtual QString displayName() const;
virtual bool requiresRasterization() const;
virtual bool containsAdvancedEffects() const;
void adjustBoxSize();
%Docstring

View File

@ -1366,6 +1366,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
friend class QgsLayout;
friend class QgsLayoutItemGroup;
friend class QgsLayoutItemMap;
friend class QgsLayoutItemLegend;
friend class QgsCompositionConverter;
};

View File

@ -856,6 +856,15 @@ QString QgsLayoutItemLegend::displayName() const
}
}
bool QgsLayoutItemLegend::requiresRasterization() const
{
return blendMode() != QPainter::CompositionMode_SourceOver;
}
bool QgsLayoutItemLegend::containsAdvancedEffects() const
{
return mEvaluatedOpacity < 1.0;
}
void QgsLayoutItemLegend::setupMapConnections( QgsLayoutItemMap *map, bool connectSlots )
{

View File

@ -133,6 +133,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
QgsLayoutItem::Flags itemFlags() const override;
//Overridden to show legend title
QString displayName() const override;
bool requiresRasterization() const override;
bool containsAdvancedEffects() const override;
/**
* Sets the legend's item bounds to fit the whole legend content.

View File

@ -12,8 +12,15 @@ __copyright__ = 'Copyright 2017, The QGIS Project'
import os
from time import sleep
from qgis.PyQt.QtCore import QDir, QRectF
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtCore import (
Qt,
QRectF
)
from qgis.PyQt.QtGui import (
QColor,
QImage,
QPainter
)
from qgis.PyQt.QtXml import QDomDocument
from qgis.core import (
@ -51,6 +58,8 @@ from qgis.core import (
QgsReadWriteContext,
QgsTextFormat,
QgsFeatureRequest,
QgsLayoutItemShape,
QgsSimpleFillSymbolLayer
)
import unittest
from qgis.testing import start_app, QgisTestCase
@ -73,6 +82,213 @@ class TestQgsLayoutItemLegend(QgisTestCase, LayoutItemTestCase):
def control_path_prefix(cls):
return "composer_legend"
def test_opacity(self):
"""
Test rendering the legend with opacity
"""
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
legend = QgsLayoutItemLegend(layout)
legend.setTitle("Legend")
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
layout.addLayoutItem(legend)
text_format = QgsTextFormat()
text_format.setFont(QgsFontUtils.getStandardTestFont("Bold"))
text_format.setSize(16)
for legend_item in [QgsLegendStyle.Title, QgsLegendStyle.Group, QgsLegendStyle.Subgroup,
QgsLegendStyle.Symbol, QgsLegendStyle.SymbolLabel]:
style = legend.style(legend_item)
style.setTextFormat(text_format)
legend.setStyle(legend_item, style)
legend.setItemOpacity(0.3)
self.assertFalse(
legend.requiresRasterization()
)
self.assertTrue(
legend.containsAdvancedEffects()
)
self.assertTrue(
self.render_layout_check('composerlegend_opacity', layout)
)
def test_opacity_rendering_designer_preview(self):
"""
Test rendering of legend opacity while in designer dialogs
"""
p = QgsProject()
l = QgsLayout(p)
self.assertTrue(l.renderContext().isPreviewRender())
l.initializeDefaults()
legend = QgsLayoutItemLegend(l)
legend.setTitle("Legend")
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
l.addLayoutItem(legend)
text_format = QgsTextFormat()
text_format.setFont(QgsFontUtils.getStandardTestFont("Bold"))
text_format.setSize(16)
for legend_item in [QgsLegendStyle.Title, QgsLegendStyle.Group, QgsLegendStyle.Subgroup,
QgsLegendStyle.Symbol, QgsLegendStyle.SymbolLabel]:
style = legend.style(legend_item)
style.setTextFormat(text_format)
legend.setStyle(legend_item, style)
legend.setItemOpacity(0.3)
page_item = l.pageCollection().page(0)
paper_rect = QRectF(page_item.pos().x(),
page_item.pos().y(),
page_item.rect().width(),
page_item.rect().height())
im = QImage(1122, 794, QImage.Format_ARGB32)
im.fill(Qt.transparent)
im.setDotsPerMeterX(int(300 / 25.4 * 1000))
im.setDotsPerMeterY(int(300 / 25.4 * 1000))
painter = QPainter(im)
painter.setRenderHint(QPainter.Antialiasing, True)
l.render(painter, QRectF(0, 0, painter.device().width(), painter.device().height()), paper_rect)
painter.end()
self.assertTrue(self.image_check('composerlegend_opacity',
'composerlegend_opacity',
im, allowed_mismatch=0))
def test_blend_mode(self):
"""
Test rendering the legend with a blend mode
"""
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
item1 = QgsLayoutItemShape(layout)
item1.attemptSetSceneRect(QRectF(20, 20, 150, 100))
item1.setShapeType(QgsLayoutItemShape.Rectangle)
simple_fill = QgsSimpleFillSymbolLayer()
fill_symbol = QgsFillSymbol()
fill_symbol.changeSymbolLayer(0, simple_fill)
simple_fill.setColor(QColor(0, 100, 50))
simple_fill.setStrokeColor(Qt.black)
item1.setSymbol(fill_symbol)
layout.addLayoutItem(item1)
legend = QgsLayoutItemLegend(layout)
legend.setTitle("Legend")
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
layout.addLayoutItem(legend)
text_format = QgsTextFormat()
text_format.setFont(QgsFontUtils.getStandardTestFont("Bold"))
text_format.setSize(16)
for legend_item in [
QgsLegendStyle.Title,
QgsLegendStyle.Group,
QgsLegendStyle.Subgroup,
QgsLegendStyle.Symbol,
QgsLegendStyle.SymbolLabel,
]:
style = legend.style(legend_item)
style.setTextFormat(text_format)
legend.setStyle(legend_item, style)
legend.setBlendMode(QPainter.CompositionMode_Darken)
self.assertTrue(legend.requiresRasterization())
self.assertTrue(self.render_layout_check("composerlegend_blendmode", layout))
def test_blend_mode_designer_preview(self):
"""
Test rendering the legend with a blend mode
"""
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
self.assertTrue(layout.renderContext().isPreviewRender())
item1 = QgsLayoutItemShape(layout)
item1.attemptSetSceneRect(QRectF(20, 20, 150, 100))
item1.setShapeType(QgsLayoutItemShape.Rectangle)
simple_fill = QgsSimpleFillSymbolLayer()
fill_symbol = QgsFillSymbol()
fill_symbol.changeSymbolLayer(0, simple_fill)
simple_fill.setColor(QColor(0, 100, 50))
simple_fill.setStrokeColor(Qt.black)
item1.setSymbol(fill_symbol)
layout.addLayoutItem(item1)
legend = QgsLayoutItemLegend(layout)
legend.setTitle("Legend")
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
layout.addLayoutItem(legend)
text_format = QgsTextFormat()
text_format.setFont(QgsFontUtils.getStandardTestFont("Bold"))
text_format.setSize(16)
for legend_item in [
QgsLegendStyle.Title,
QgsLegendStyle.Group,
QgsLegendStyle.Subgroup,
QgsLegendStyle.Symbol,
QgsLegendStyle.SymbolLabel,
]:
style = legend.style(legend_item)
style.setTextFormat(text_format)
legend.setStyle(legend_item, style)
legend.setBlendMode(QPainter.CompositionMode_Darken)
page_item = layout.pageCollection().page(0)
paper_rect = QRectF(
page_item.pos().x(),
page_item.pos().y(),
page_item.rect().width(),
page_item.rect().height(),
)
im = QImage(1122, 794, QImage.Format_ARGB32)
im.fill(Qt.transparent)
im.setDotsPerMeterX(int(300 / 25.4 * 1000))
im.setDotsPerMeterY(int(300 / 25.4 * 1000))
painter = QPainter(im)
painter.setRenderHint(QPainter.Antialiasing, True)
layout.render(
painter,
QRectF(0, 0, painter.device().width(), painter.device().height()),
paper_rect,
)
painter.end()
self.assertTrue(
self.image_check(
"composerlegend_blendmode", "composerlegend_blendmode", im, allowed_mismatch=0
)
)
def testInitialSizeSymbolMapUnits(self):
"""
Test initial size of legend with a symbol size in map units

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB