[FEATURE] Paint effect support for label buffers

Allows applying a paint effect (such as blurs) to label buffers.
Blurring label buffers is a great way to get the text readability
of a label buffer without the distracting halo effect.
This commit is contained in:
Nyall Dawson 2017-04-28 10:20:20 +10:00
parent f0fb1f184a
commit b9f102c98d
9 changed files with 242 additions and 99 deletions

View File

@ -130,7 +130,19 @@ class QgsTextBufferSettings
*/
void setBlendMode( QPainter::CompositionMode mode );
/** Reads settings from a layer's custom properties.
/** Returns the current paint effect for the buffer.
* @returns paint effect
* @see setPaintEffect()
*/
QgsPaintEffect* paintEffect() const;
/** Sets the current paint effect for the buffer.
* @param effect paint effect. Ownership is transferred to the buffer settings.
* @see paintEffect()
*/
void setPaintEffect( QgsPaintEffect* effect /Transfer/ );
/** Reads settings from a layer's custom properties.
* @param layer source vector layer
* @see writeToLayer()
*/

View File

@ -15,12 +15,14 @@
***************************************************************************/
#include "qgstextrenderer.h"
#include "qgis.h"
#include "qgstextrenderer_p.h"
#include "qgsfontutils.h"
#include "qgsvectorlayer.h"
#include "qgssymbollayerutils.h"
#include "qgspainting.h"
#include "qgsmarkersymbollayer.h"
#include "qgspainteffectregistry.h"
#include <QFontDatabase>
Q_GUI_EXPORT extern int qt_defaultDpiX();
@ -179,6 +181,17 @@ void QgsTextBufferSettings::setBlendMode( QPainter::CompositionMode mode )
d->blendMode = mode;
}
QgsPaintEffect *QgsTextBufferSettings::paintEffect() const
{
return d->paintEffect;
}
void QgsTextBufferSettings::setPaintEffect( QgsPaintEffect *effect )
{
delete d->paintEffect;
d->paintEffect = effect;
}
void QgsTextBufferSettings::readFromLayer( QgsVectorLayer *layer )
{
// text buffer
@ -236,6 +249,16 @@ void QgsTextBufferSettings::readFromLayer( QgsVectorLayer *layer )
d->joinStyle = static_cast< Qt::PenJoinStyle >( layer->customProperty( QStringLiteral( "labeling/bufferJoinStyle" ), QVariant( Qt::RoundJoin ) ).toUInt() );
d->fillBufferInterior = !layer->customProperty( QStringLiteral( "labeling/bufferNoFill" ), QVariant( false ) ).toBool();
if ( layer->customProperty( QStringLiteral( "labeling/bufferEffect" ) ).isValid() )
{
QDomDocument doc( QStringLiteral( "effect" ) );
doc.setContent( layer->customProperty( QStringLiteral( "labeling/bufferEffect" ) ).toString() );
QDomElement effectElem = doc.firstChildElement( QStringLiteral( "effect" ) ).firstChildElement( QStringLiteral( "effect" ) );
setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
}
else
setPaintEffect( nullptr );
}
void QgsTextBufferSettings::writeToLayer( QgsVectorLayer *layer ) const
@ -249,6 +272,21 @@ void QgsTextBufferSettings::writeToLayer( QgsVectorLayer *layer ) const
layer->setCustomProperty( QStringLiteral( "labeling/bufferOpacity" ), d->opacity );
layer->setCustomProperty( QStringLiteral( "labeling/bufferJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
layer->setCustomProperty( QStringLiteral( "labeling/bufferBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect ) )
{
QDomDocument doc( QStringLiteral( "effect" ) );
QDomElement effectElem = doc.createElement( QStringLiteral( "effect" ) );
d->paintEffect->saveProperties( doc, effectElem );
QString effectProps;
QTextStream stream( &effectProps );
effectElem.save( stream, -1 );
layer->setCustomProperty( QStringLiteral( "labeling/bufferEffect" ), effectProps );
}
else
{
layer->removeCustomProperty( QStringLiteral( "labeling/bufferEffect" ) );
}
}
void QgsTextBufferSettings::readXml( const QDomElement &elem )
@ -309,6 +347,11 @@ void QgsTextBufferSettings::readXml( const QDomElement &elem )
static_cast< QgsPainting::BlendMode >( textBufferElem.attribute( QStringLiteral( "bufferBlendMode" ), QString::number( QgsPainting::BlendNormal ) ).toUInt() ) );
d->joinStyle = static_cast< Qt::PenJoinStyle >( textBufferElem.attribute( QStringLiteral( "bufferJoinStyle" ), QString::number( Qt::RoundJoin ) ).toUInt() );
d->fillBufferInterior = !textBufferElem.attribute( QStringLiteral( "bufferNoFill" ), QStringLiteral( "0" ) ).toInt();
QDomElement effectElem = textBufferElem.firstChildElement( QStringLiteral( "effect" ) );
if ( !effectElem.isNull() )
setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
else
setPaintEffect( nullptr );
}
QDomElement QgsTextBufferSettings::writeXml( QDomDocument &doc ) const
@ -324,6 +367,8 @@ QDomElement QgsTextBufferSettings::writeXml( QDomDocument &doc ) const
textBufferElem.setAttribute( QStringLiteral( "bufferOpacity" ), d->opacity );
textBufferElem.setAttribute( QStringLiteral( "bufferJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
textBufferElem.setAttribute( QStringLiteral( "bufferBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect ) )
d->paintEffect->saveProperties( doc, textBufferElem );
return textBufferElem;
}
@ -1809,9 +1854,25 @@ void QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRender
QPicture buffPict;
QPainter buffp;
buffp.begin( &buffPict );
buffp.setPen( pen );
buffp.setBrush( tmpColor );
buffp.drawPath( path );
if ( buffer.paintEffect() && buffer.paintEffect()->enabled() )
{
context.setPainter( &buffp );
buffer.paintEffect()->begin( context );
context.painter()->setPen( pen );
context.painter()->setBrush( tmpColor );
context.painter()->drawPath( path );
buffer.paintEffect()->end( context );
context.setPainter( p );
}
else
{
buffp.setPen( pen );
buffp.setBrush( tmpColor );
buffp.drawPath( path );
}
buffp.end();
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer )
@ -1822,7 +1883,6 @@ void QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRender
bufferComponent.pictureBuffer = penSize / 2.0;
drawShadow( context, bufferComponent, format );
}
p->save();
if ( context.useAdvancedEffects() )
{
@ -2562,3 +2622,4 @@ void QgsTextRenderer::drawTextInternal( TextPart drawType,
i++;
}
}

View File

@ -31,6 +31,7 @@ class QgsTextBackgroundSettingsPrivate;
class QgsTextShadowSettingsPrivate;
class QgsTextSettingsPrivate;
class QgsVectorLayer;
class QgsPaintEffect;
/** \class QgsTextBufferSettings
* \ingroup core
@ -191,6 +192,17 @@ class CORE_EXPORT QgsTextBufferSettings
*/
QDomElement writeXml( QDomDocument &doc ) const;
/** Returns the current paint effect for the buffer.
* \returns paint effect
* \see setPaintEffect()
*/
QgsPaintEffect *paintEffect() const;
/** Sets the current paint \a effect for the buffer.
* \param effect paint effect. Ownership is transferred to the buffer settings.
* \see paintEffect()
*/
void setPaintEffect( QgsPaintEffect *effect );
private:

View File

@ -22,6 +22,7 @@
#include "qgsmapunitscale.h"
#include "qgsunittypes.h"
#include "qgsapplication.h"
#include "qgspainteffect.h"
#include <QSharedData>
#include <QPainter>
@ -50,6 +51,7 @@ class CORE_EXPORT QgsTextBufferSettingsPrivate : public QSharedData
, fillBufferInterior( false )
, joinStyle( Qt::RoundJoin )
, blendMode( QPainter::CompositionMode_SourceOver )
, paintEffect( nullptr )
{
}
@ -64,9 +66,15 @@ class CORE_EXPORT QgsTextBufferSettingsPrivate : public QSharedData
, fillBufferInterior( other.fillBufferInterior )
, joinStyle( other.joinStyle )
, blendMode( other.blendMode )
, paintEffect( other.paintEffect ? other.paintEffect->clone() : nullptr )
{
}
~QgsTextBufferSettingsPrivate()
{
delete paintEffect;
}
bool enabled;
double size;
QgsUnitTypes::RenderUnit sizeUnit;
@ -76,6 +84,7 @@ class CORE_EXPORT QgsTextBufferSettingsPrivate : public QSharedData
bool fillBufferInterior;
Qt::PenJoinStyle joinStyle;
QPainter::CompositionMode blendMode;
QgsPaintEffect *paintEffect;
};

View File

@ -467,7 +467,16 @@ void QgsEffectStackCompactWidget::showDialog()
}
connect( widget, &QgsPanelWidget::widgetChanged, this, &QgsEffectStackCompactWidget::updateEffectLive );
connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsEffectStackCompactWidget::updateAcceptWidget );
openPanel( widget );
QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
if ( panel && panel->dockMode() )
{
panel->openPanel( widget );
}
else
{
openPanel( widget );
}
}
void QgsEffectStackCompactWidget::enableToggled( bool checked )

View File

@ -24,6 +24,8 @@
#include "qgssubstitutionlistwidget.h"
#include "qgspallabeling.h" // for enum values
#include "qgssettings.h"
#include "qgseffectstack.h"
#include "qgspainteffectregistry.h"
QgsTextFormatWidget::QgsTextFormatWidget( const QgsTextFormat &format, QgsMapCanvas *mapCanvas, QWidget *parent )
: QWidget( parent )
@ -259,6 +261,10 @@ void QgsTextFormatWidget::initWidget()
mLabelingOptionsListWidget->setCurrentRow( settings.value( QStringLiteral( "Windows/Labeling/Tab" ), 0 ).toInt() );
mBufferEffect.reset( QgsPaintEffectRegistry::defaultStack() );
connect( mBufferEffectWidget, &QgsEffectStackCompactWidget::changed, this, &QgsTextFormatWidget::updatePreview );
mBufferEffectWidget->setPaintEffect( mBufferEffect.get() );
setDockMode( false );
@ -611,7 +617,14 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format )
mBufferJoinStyleComboBox->setPenJoinStyle( buffer.joinStyle() );
mBufferTranspFillChbx->setChecked( buffer.fillBufferInterior() );
comboBufferBlendMode->setBlendMode( buffer.blendMode() );
if ( buffer.paintEffect() )
mBufferEffect.reset( buffer.paintEffect()->clone() );
else
{
mBufferEffect.reset( QgsPaintEffectRegistry::defaultStack() );
mBufferEffect->setEnabled( false );
}
mBufferEffectWidget->setPaintEffect( mBufferEffect.get() );
mFontSizeUnitWidget->setUnit( format.sizeUnit() );
mFontSizeUnitWidget->setMapUnitScale( format.sizeMapUnitScale() );
@ -735,6 +748,10 @@ QgsTextFormat QgsTextFormatWidget::format() const
buffer.setJoinStyle( mBufferJoinStyleComboBox->penJoinStyle() );
buffer.setFillBufferInterior( mBufferTranspFillChbx->isChecked() );
buffer.setBlendMode( comboBufferBlendMode->blendMode() );
if ( mBufferEffect && !QgsPaintEffectRegistry::isDefaultStack( mBufferEffect.get() ) )
buffer.setPaintEffect( mBufferEffect->clone() );
else
buffer.setPaintEffect( nullptr );
format.setBuffer( buffer );
// shape background

View File

@ -136,6 +136,7 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextForm
Mode mWidgetMode;
QgsMapCanvas *mMapCanvas = nullptr;
QgsCharacterSelectorDialog *mCharDlg = nullptr;
std::unique_ptr< QgsPaintEffect > mBufferEffect;
QFontDatabase mFontDB;

189
src/ui/qgstextformatwidgetbase.ui Normal file → Executable file
View File

@ -112,7 +112,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>482</width>
<width>487</width>
<height>300</height>
</rect>
</property>
@ -591,7 +591,7 @@
<item>
<widget class="QStackedWidget" name="mLabelStackedWidget">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="mLabelPage_Text">
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -620,8 +620,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>448</width>
<height>442</height>
<width>324</width>
<height>360</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -1443,8 +1443,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>383</width>
<height>389</height>
<width>293</width>
<height>315</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
@ -2078,8 +2078,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>301</width>
<height>280</height>
<width>465</width>
<height>385</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
@ -2223,6 +2223,80 @@ font-style: italic;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="mBufferTranspLabel_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Transparency</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelBufferBlend">
<property name="text">
<string>Blend mode</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferTranspDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferJoinStyleDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferSizeDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferColorDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_31">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Size</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QgsBlendModeComboBox" name="comboBufferBlendMode"/>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="mBufferTranspFillChbx">
<property name="text">
<string>Color buffer's fill</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QFrame" name="frame_5">
<property name="sizePolicy">
@ -2291,37 +2365,6 @@ font-style: italic;</string>
</layout>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="mBufferTranspLabel_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Transparency</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelBufferBlend">
<property name="text">
<string>Blend mode</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferTranspDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferJoinStyleDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QgsColorButton" name="btnBufferColor">
<property name="sizePolicy">
@ -2347,49 +2390,6 @@ font-style: italic;</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferSizeDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsPropertyOverrideButton" name="mBufferColorDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_31">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Size</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QgsBlendModeComboBox" name="comboBufferBlendMode"/>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="mBufferTranspFillChbx">
<property name="text">
<string>Color buffer's fill</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsUnitSelectionWidget" name="mBufferUnitWidget" native="true">
<property name="focusPolicy">
@ -2397,6 +2397,9 @@ font-style: italic;</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="QgsEffectStackCompactWidget" name="mBufferEffectWidget" native="true"/>
</item>
</layout>
</widget>
</item>
@ -2464,8 +2467,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>448</width>
<height>731</height>
<width>372</width>
<height>587</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
@ -3300,8 +3303,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>330</width>
<height>447</height>
<width>281</width>
<height>369</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_22">
@ -3805,8 +3808,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>430</width>
<height>917</height>
<width>307</width>
<height>770</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
@ -5443,8 +5446,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>429</width>
<height>799</height>
<width>302</width>
<height>665</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
@ -6492,6 +6495,12 @@ font-style: italic;</string>
<extends>QLabel</extends>
<header>qgstextpreview.h</header>
</customwidget>
<customwidget>
<class>QgsEffectStackCompactWidget</class>
<extends>QWidget</extends>
<header>effects/qgseffectstackpropertieswidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mFieldExpressionWidget</tabstop>

View File

@ -26,8 +26,9 @@ from qgis.core import (QgsTextBufferSettings,
QgsMapSettings,
QgsRenderContext,
QgsRectangle,
QgsRenderChecker)
from qgis.PyQt.QtGui import (QColor, QPainter, QImage, QBrush, QPen)
QgsRenderChecker,
QgsBlurEffect)
from qgis.PyQt.QtGui import (QColor, QPainter, QFont, QImage, QBrush, QPen)
from qgis.PyQt.QtCore import (Qt, QSizeF, QPointF, QRectF, QDir)
from qgis.PyQt.QtXml import QDomDocument
from qgis.testing import unittest, start_app
@ -1168,6 +1169,18 @@ class PyQgsTextRenderer(unittest.TestCase):
format.buffer().setSize(2)
format.buffer().setSizeUnit(QgsUnitTypes.RenderMillimeters)
format.buffer().setFillBufferInterior(True)
#assert self.checkRender(format, 'text_buffer_interior', QgsTextRenderer.Buffer, text=['test'])
def testDrawBufferEffect(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setFont(getTestFont('bold'))
format.setSize(60)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
format.buffer().setEnabled(True)
format.buffer().setSize(2)
format.buffer().setSizeUnit(QgsUnitTypes.RenderMillimeters)
format.buffer().setPaintEffect( QgsBlurEffect.create({'blur_level':'10','enabled':'1'}) )
assert self.checkRender(format, 'text_buffer_interior', QgsTextRenderer.Buffer, text=['test'])
def testDrawShadow(self):