From 3588d41786ee2f3fd2b346e1db24e4b525399323 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 10 Oct 2023 12:07:49 +1000 Subject: [PATCH] Fix legend item with opacity forces whole layout to rasterize and expand test coverage of blend modes and opacity with legend items --- .../layout/qgslayoutitemlegend.sip.in | 4 + src/core/layout/qgslayoutitem.h | 1 + src/core/layout/qgslayoutitemlegend.cpp | 9 + src/core/layout/qgslayoutitemlegend.h | 2 + tests/src/python/test_qgslayoutlegend.py | 220 +++++++++++++++++- .../expected_composerlegend_blendmode.png | Bin 0 -> 5683 bytes ...expected_composerlegend_blendmode_mask.png | Bin 0 -> 7606 bytes .../expected_composerlegend_opacity.png | Bin 0 -> 5142 bytes .../expected_composerlegend_opacity_mask.png | Bin 0 -> 7000 bytes 9 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 tests/testdata/control_images/composer_legend/expected_composerlegend_blendmode/expected_composerlegend_blendmode.png create mode 100644 tests/testdata/control_images/composer_legend/expected_composerlegend_blendmode/expected_composerlegend_blendmode_mask.png create mode 100644 tests/testdata/control_images/composer_legend/expected_composerlegend_opacity/expected_composerlegend_opacity.png create mode 100644 tests/testdata/control_images/composer_legend/expected_composerlegend_opacity/expected_composerlegend_opacity_mask.png diff --git a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in index 2220441a9b6..24076990311 100644 --- a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in @@ -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 diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 9f692ddaa37..99be03fb50e 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -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; }; diff --git a/src/core/layout/qgslayoutitemlegend.cpp b/src/core/layout/qgslayoutitemlegend.cpp index f972dc6397a..3ed55d97dcb 100644 --- a/src/core/layout/qgslayoutitemlegend.cpp +++ b/src/core/layout/qgslayoutitemlegend.cpp @@ -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 ) { diff --git a/src/core/layout/qgslayoutitemlegend.h b/src/core/layout/qgslayoutitemlegend.h index 279fa65c5df..39d7410f595 100644 --- a/src/core/layout/qgslayoutitemlegend.h +++ b/src/core/layout/qgslayoutitemlegend.h @@ -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. diff --git a/tests/src/python/test_qgslayoutlegend.py b/tests/src/python/test_qgslayoutlegend.py index 5e802c177c3..cd475bc1c93 100644 --- a/tests/src/python/test_qgslayoutlegend.py +++ b/tests/src/python/test_qgslayoutlegend.py @@ -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 diff --git a/tests/testdata/control_images/composer_legend/expected_composerlegend_blendmode/expected_composerlegend_blendmode.png b/tests/testdata/control_images/composer_legend/expected_composerlegend_blendmode/expected_composerlegend_blendmode.png new file mode 100644 index 0000000000000000000000000000000000000000..5f92f60b6955685a783747ac170956a0bf79b8f6 GIT binary patch literal 5683 zcmeHLYfzI{8a{+nE?dN^l`R6*8j_IP6!g7_}?NQ zLilZ0|7ixbBDegyRB{K}!SUI6a`?;l&s9V6%cuUjt%tW85&YS;6IB#L9(6ve^j!2? zOx|#NTyb5T(bzGd3H0~Y=JFSC6A=*++W^QrI^h-sgA%FtmAr;{$PR#OXJNFC5E6hL zA36c>n}nSJeEib^IQY9zIObhj;D44L+F>Th^I2>VdzpI)xsCJh<$W7VlP79;Y|Fg| zKyUy;u53(ZX)1UoNz$I6H|iVOmRlD%(dm);-6GCxHTLHNxU5!Y8oP=Wnc5%{3H}DX zm@o7_!bAeqw{&%q6tisF(4mBxv@wkR6w=j;n}Leb?*O8`i;}_nY3Qbl=JJCjd6+J3 zTC^SsZxaIHLwy2umLcl_{jd)cAt*RCs95O^Mp+77eV9c)T8mk0?RM*^hY_RycovZt z+`9VXUbmobPBUE_2O--V6qydcM3!!c0{ikkcoOz_HhFZPgMSXLt)00d4%2m94@ZE` zgAkGuwYd<5j$u4!WhzgG*n#lwPw78X?e%M!v=Y+d{P0#&suJf$3S`p^PQ=^{bw28; zAWavd<<^kNYSnlcG4%(_4YVsRfPfFe(g(0ge|F-wO=PeK)7cdxz)-p#G_1QCi zUfjrBMjY$1LCZ6m=MW;98uO0mkggwU<51O=H`J9-j#=&N8<@L__bk`&NI@3aIUPO& znsAi}Q!?=EWJ6u_=2eJa+qQOlzfNSY6G`&zsaw%s%ZYqSmo!kn=@HXkebqsulGKJ# z!^N*Or~%*H1}00HL$w-A`lAmvwh$uqOf@#|Q}xIvt*Qdkn3TmxrY)C4Ci$Sczc^{G zLW!51(UhZ!F@7I>AjXlFj}soI+=p?=s!Sp&oC&l}byY_S!Q_s*m1LdnvwW9Q$BJ!HaA;R@YO9Ji(!7hDoO+?Mivr7LB!w65| z`*iaqvF2BGq`UTLH1+XcbaP_Ovr3n`&8kP~E$X{1G_yqFkLGod*ZYi<7%gd>aUUxi zP~vs9a}ZP-*H?hDGS*dV{ThN%B8g%5DB99poq@o;EfkdX-ris1fEp{)xy95oDaKtj z4$4^L%ls>1jBx7!X?ol;tL!ignvX@bv#kv?HOHb>W?N^7?l0ctZoNby`% z^`j6}1(Azk&JK;BvEk3XO&1r62$@(#rC1~6hdwlJS$-@mez=$#=fPX#r{`k|>?(oM~pMABTEB$mds zq(5czCG-wzs_!L@gd3z&P|;|*@8ET}fgZO(V`nE*{pg4=(<_%jRu}eDhov#?%T-ne z&5JqTJdqfX{)(7BW|pog{`rv>G7U*O%^y>M;CB$R9`d-8HCv$@<_$<#)T|EEUN`pi znn^(?X0&2ijO{3{CswWHs8Pp$|2-a`#0|*c<}%6Z{)|hH6||m);_I*a)>w&TGKWKs ze9x}_i3}M&Oi|H~e z#qx(_+ERdJpElRXnqE&oD4Z^eGwcu3wZ*!EJ^e5zlUJiBr3M*POrrgxkUxnVjT5eh zWyev;T8T2IrK@kI{L;8`CAQtr$TuIsfeRnOY>1Yp;9aMwDQr4TGcb&G2Ic;XhwM@# zL&&(!Ky*{Kv79~?xcbB+h`BgIh`VynXa_o>vxu<`E*r=8Fes=Rj`481I~ZF_*eT*X zN?9`TG-NI$KdN&m^l}0NjvqS3FHAHIJva&gzqFVF-$k~9Xp;dp@nh45Hbwkj`hc6B zgsAU@uavG~D9ntk)EB5unH@ z9ZGAqZY{x!JeSny$jC_jn{d@FyBR$MPc~m_bK|xigst;pYfRZ{*8gi0@}|{6?LxGk Ta>^3G6%B+2ho7!H8DH>kCkq2R literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/composer_legend/expected_composerlegend_blendmode/expected_composerlegend_blendmode_mask.png b/tests/testdata/control_images/composer_legend/expected_composerlegend_blendmode/expected_composerlegend_blendmode_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c9b883abd3b7fb585c5defe001ca3234552046 GIT binary patch literal 7606 zcmeHMYgbcO8s3V7wiU2tsa!M^X~%JjB316hOy#DffEEr27_efECPGk5xWo`_RdD3e zE`?U)qIE{dfh0l=w-AEXhOu%Pfh61#Kq^rJ0fi)(5HkDtp+CS6^PwwQE9;!I&&l5J z<$0cW=RKDXgojwY>+miBz-s@#z%KzLdW0PwWr;EGnaHMsA?|pfPp4RF))l?dFeEH{?fLEH1tn#9#W@_eAiCAo_<7^Ng`q z^6T?1Sf9OVVHb40xR_n@@KRtdpWOYavUfz6J7=W%7^{|cG5bCY%)>_4ybnzVV6XYspDY^RZM%xkvE3gT z*>}ulMGou9z`y`J0XdQR_U@ppeia>n-p)K9%jIt{{BC-0*IVJ>`lo{y0EBObADRS- zK@dqB(?FgX;$QXO0&-j-(h7U!>7(*P>e*RdfumRW(-YyY*u(e#vN4KErSk9Hi;9Sd zU@ng5W(R<=Yt9cr`@t-Av(n+QvVx)hsBjg?u`M7y#4vhcjXT)+&hhTDSR#?=ktEK) z?W?Z+x!ZS=5?=1h_x1Cu5ekhz_P8^&(=NEQ(9lpCogPi0sN556z}T?~=|^Bq*4+Ko zl#Glf&*Y&ff@vwu!^=xH(wwGxdc}EAI$**5M(?DmSlcY*!v}jqca_(@8yH`Egc|Fo-G)iQ$7}3(J(6Guq!lB{e8j*@+CdUDT_6x_a7#GsN1v{a{rNaTB9Rb;Ip5_)gPs-nsn4&*Rg)?*?*s=2J5cT8 z+RkNeKiqV;dyio{Yr3w(n$h)fXi5Lr*b$*d8ZUYBScKE{=gCntM1F zkH>5N8(<@`CzT!S>gtjV4>PWbaG?FsFZp)ByBh;x1YQ%B#1!Y-V*!Y0nytVy$iH3N z#;1<9p?Vm>^VdHK{b2`3r(p0PcpMDrbh@;0G}~{y8A8?ZNz#kne`{V;RZ2sgnEeBa zzP?Bpe17_@qST9;z=Sxptxy*FsjE?PnW~h+WkY0SnT$GBu2Xh(U$`HqZniikAr?V~ z8>zfdH{rdwxVWjuZlc%}62TEIKrQz0M7R&g3VOT3bY`VUojP^3+3A`zNlrqSt%owPPKvb?;!fTDm=M3915 z_u+=*n%DEnh>YoGWko!^qSV@0SxMH%96pF32Hh;-n5i;b&qmP=u7?gFlDwS=%d+(1 zdmC|2ep~^o4eR{Zyir3t4eHU? zd)IG1)Y#2S;7bY1TjD>VgIoCjuamBA7 zU&)HrOny>FAx8NcZVQ^gnBo6Yn0(~O&AO%EuSP^eyt_L!-5`Xu| zXD*jI%l#VNcij<*L@fe=>eXX6r$*ByD@U?XubJ30+(M(2XX{@SDC-wA8U(IABNsG! zQWgH{hAL5T^@SERjWYadDAIrP1)9 zd9<}M3XybqjlNMW-6%^0XIm=bLHmCC0MkMqkGElaTogtDPJE-Zg{C6QTWAy7=I1|M znSv<7vQ%I=*HeL)$mPTvp2;XtK$ocH0u|I)E{Z|m$?0KIPX$pfmz$n?apHtowC%C=$42;`DI}zKjYZ3bp9fkqyG4Oa0h}kj+_ZKDhZ~O zBVR61t1wsdE+hB%E??e#+7A+`P42+LgeZByoFbBG4 zng|XFQC;3zQH`RoMwEI`qOGj0rC8aAb&xhI)I}rLtze2)rc!zjrcyotS8qXjM#EYJ z{o$u6yUEGP9Q$3zcQ@GB*vR1T|9biY+<h`|f?LXfM#fo$5)~!c4 zYXCy_=lQ$~ISs&E#O??!wi^K9>)#CgBM%w{z`8Ae>=EFVv-uVlB5cQGj)oZIXKRH`G9?&y1eQ4 zJ$$G-<|e5IT}N~w{)`S-U! MC_Iq6_sBQ@17OKaX8-^I literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/composer_legend/expected_composerlegend_opacity/expected_composerlegend_opacity.png b/tests/testdata/control_images/composer_legend/expected_composerlegend_opacity/expected_composerlegend_opacity.png new file mode 100644 index 0000000000000000000000000000000000000000..a3dc6c5cbe09a3d320a1634c69c6480927e9dcd5 GIT binary patch literal 5142 zcmeHJZA?>V6n+aDghAOL5v>N?M2MSd5wT(m)G-7x2HA&00Y8vV2z0bTI-p-P;vkMy zoOMA4ElV6jMNn6y76xThtwu}dumU3GqtsH06k1AezhEyvmi?N4{JGxb-g9&B`<|0? z&htL!IhUG}IOSrtLd&8UVCxdPGHle4 zaN=3Xx{CLp!)1D#0R`=1)d3i0P3Z3=ZMISu zBCGT#)=xGKz5Ll`G8Igp`n3ILdWdyD_UK`(q|=1DTuqoe*Z8jHnZ zFzkIlSgqE_+CqY$@094~ZnH+CIeB|s#rWhTnIPaOGK50m(vrjYh-@0BO1tP!Mn*>R zzPC25=DNc9V6l%C7gCYsk{`nH+Syn2cDr4x{mO#q_4?V_S(C{m=nBLi$M6!xV)2!y zxiP_I_62>5Yzfg+8w>`OMyJ!s<#IlsFA|Bw5AI4=uC)DNV^q)xYNn>Sxml6O&^1C! zucyRGEL}>alG+vJE6c#~60guVx5UQ8P&gwk**7}S0DVan%8mW4_M@PnApWCCmDy}# z-PyD7VPhQI9u-`jM9ul@dCOu&)@7y6utS>uIfL$eg&9yN6wk{CvR|sz*tJ#GrR(69 z{6-jR<{Eu7jBtoav742of$AL5ke!R0#dC@lqz%rxCU(AeKD$?eeZiQUpP#p%g?SjO zQNZyeFQ7-r$Ui!z)oQ1va?PAOMoRN76N22eEf!;MJDTwF3yHuP2}5kWtSz(!s!c!Q zcj(Pqa3DJMOIXim8J$e(v7O>bUzy`vWbJ|H@9c-Uz#fyxy-#!7O0VP2qd+A*&okGo zxJKgc-K8ZZhhw|DyN{L8at#l>MME>QvjrB!X0uI9OeFb9YLm-cmI42m9bo3d0S{yd zze|weK;|{gIR#YearTRxJM5Qv2fu~&r-%8;Xs1t`=QVOwY?XR>S{4yQB1zLz(?^i0 zs$tUaR7qxQ+^(I|=s-ZDkpp^^?Y2^38lH;oJiJXknm?}M9Z&PBmTOvu|NL55n%~40 z_4oJF>)0Ir77(4#P6LCunwlEeZ)#VE#HXmJXkq%*-hTZ;3p0e*-Ij(F5nSQfOG=0x zo#(Ix%`IjW~++L?pzYzEXG-TE26A(aA7@izD)W9HV)+7_N2q5@K@3ApI*_@mJ7yF2o4tUOS0Cv>wi~%X+jL0O_U;XW)@`ldyY6xhJz*kGSR9TZ+_s z|uMo08BPf8xyFLyFA2 literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/composer_legend/expected_composerlegend_opacity/expected_composerlegend_opacity_mask.png b/tests/testdata/control_images/composer_legend/expected_composerlegend_opacity/expected_composerlegend_opacity_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..5cd6a27e34f8556ba7299cc15e2df26c3c6fcb44 GIT binary patch literal 7000 zcmeHMdr+F$6~D&Bn$&2VPBq2{G1;z7YJ)^YV-PmG?ij~aH%(W~Cc&&`eZ|F!Evte^ z+e|QxO1nz=uAg|sp`J>Z6I=g@F?7$4) z_rbmA+;h)4zw@}??RT@&4<0yj003|><9yoB0N7gyfbS81Klsm#dRYi>`+sr%QZ)cU zA0S_!KNI@O00=*jk#_FqHQjo}M;G6}^!L9!Js5HB_UmgN&tyk7?Od=S=&Gsl0!OPg zOl@6%c=F`$qMk^Ca@AzfwB)eE2l5t`Fuo@AKaNJ#HFUzZ^Z!{dg=Jrd0ckZ z9tNQl+touGQyK}SuX%8gzMI2Av6NRI<`#9{H**_Yy5FYzfq%w9X6`@PX?V(r!i>ps z512WfGEI@-N(+<8G@DGz{zS0MKK?)6E%+BWban&7%a4^*V9%ba+V$uY<2f=ZU4)uV zXm-&FpUd^_GsQ)CPeVgqZRG&CZCZUhJu)FMz{>-_cZ&ant21? z;HJ@Lph?wg_3)!dDFz*xN<9k&gG3P;jfQZ=qn+&=la&fO8g+ z=GWp_xK+zn3s#3~SRe=s4GmSn8O+uWVpj&ay1M#wwK@^9G2UXm^<%-<7~S&XuAV~0 zsn^x*Boax&Mi*okOK7`WWi*f5HAjuaruf0w@{%==$7=;u6bg3DV$Y8aiQ(l#&JvA` z!{Tsr`&)H^eSr^i`^|QX=41A;9{5RXhkFdUGtn;7s8&}CX|CO0HJPcUP+{CBO<^kQ z@@c5=iHV710s&PZqNm=vE1vEZ56vb-mMp`GjQ6<1w9!?Gu!xgGTY=^_q z+Gu$m+pQTG7?41XFIHexJ3Bis#+dKMON=Y)g6+CgxU;I-XP2k?0HFU621@75eeExQ zWSX6wZNIOzA)5~0Ch_Gesf<$)8$!6*vCtME+({{Fs-jY2$W!kv!tL@fE ztghZTRwz+Mfc3=k1rfAwHpZ$TZU94+^L_j-5=QP&QNY^75pz z65di5@8XWbH91t|9Ft8^tu$R)A7Pis`5p)Q$LTe8(H>x%gXzK)^ATT;;m8W=r;B;e z53^Svi^bv~ULoQY5Y2cF%VM#pEa;L!=tA`f%I3-w@0T4(N=mA1(b)so3X)ZfnddE4 zAY=Xg{g9b-dwaWK+pT=0Ijp))>z^x+Wlx#ql_Hnc0f6%&5@Ot+y6B#@UNp10CMZ59 zFg`yDh05ur-cUk+k4H?(uT-{J>!QvQA{c9uXKtx`(6bQ6Fc&R z%VtaN?d?TeZ+K)Rd_-YL-Vr~@H0m+Fz$8L6>>ts#fm6<^hlJONa*KLxtr$W=`$F&v z>ysNDVvDT6Bj;BVB0^o8y>KtFA$LR@Y4<9XW6f%^U3~ge2;n**g_a0RnO)5o=cNOT zf@cX?gb1_q-+eh8iC(YoUl0yu7&2Kb7MC= zbT?rnP(M|E)73C;zB8Ku!%7W}7BQkAZBRy3+IP^YoM;}j1U;K+jv7tuDuLm$tTTJ3 zcS%o)Ss?K*$DH^ZDuE+YIe6fprX>jMeR9G&E{58AQWRcre1j z3`Hm}g*Ia%K%G=5m8hiM)Qvv!BD#1mzX^sW17;o$<6aI&?hMun9_Xpzjy=Fs{nvDF zW#TQ-ytOKXhPP;kD)4qM-Zt*L($8MsUH1XwvhooHQp&QT{Kju9=??vWJ1$t^FOpt0 zSFj>PrS1jpy|9)4a)p3{>GF>ou(tm{E&SqVp^Sog5qxWj#gi9L-xWN4Yfo*wr)%D0M(=s6H`RDkjW^YJU$1=g_e^#|IQ2~M U1qLPrDOoe#&Q2S6^V)Cz1C>N?a{vGU literal 0 HcmV?d00001