QGIS API Documentation 3.27.0-Master (c6eca784ad)
qgspainteffect.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspainteffect.cpp
3 -------------------
4 begin : December 2014
5 copyright : (C) 2014 Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgspainteffect.h"
19#include "qgsimageoperation.h"
20#include "qgslogger.h"
21#include "qgsrendercontext.h"
22#include "qgssymbollayerutils.h"
23#include <QPicture>
24
25Q_GUI_EXPORT extern int qt_defaultDpiX();
26Q_GUI_EXPORT extern int qt_defaultDpiY();
27
29 : mEnabled( other.enabled() )
30 , mDrawMode( other.drawMode() )
31{
32
33}
34
36{
37 if ( mOwnsImage )
38 {
39 delete mSourceImage;
40 }
41 delete mEffectPainter;
42 delete mTempPicture;
43}
44
45void QgsPaintEffect::setEnabled( const bool enabled )
46{
48}
49
51{
53}
54
55bool QgsPaintEffect::saveProperties( QDomDocument &doc, QDomElement &element ) const
56{
57 if ( element.isNull() )
58 {
59 return false;
60 }
61 QDomElement effectElement = doc.createElement( QStringLiteral( "effect" ) );
62 effectElement.setAttribute( QStringLiteral( "type" ), type() );
63 QgsSymbolLayerUtils::saveProperties( properties(), doc, effectElement );
64 element.appendChild( effectElement );
65 return true;
66}
67
68bool QgsPaintEffect::readProperties( const QDomElement &element )
69{
70 if ( element.isNull() )
71 {
72 return false;
73 }
74
75 const QVariantMap props = QgsSymbolLayerUtils::parseProperties( element );
76 readProperties( props );
77 return true;
78}
79
80void QgsPaintEffect::render( QPicture &picture, QgsRenderContext &context )
81{
82 //set source picture
83 mPicture = &picture;
84 delete mSourceImage;
85 mSourceImage = nullptr;
86
87 draw( context );
88}
89
91{
92 //temporarily replace painter and direct paint operations for context to a QPicture
93 mPrevPainter = context.painter();
94
95 delete mTempPicture;
96 mTempPicture = new QPicture();
97
98 delete mEffectPainter;
99 mEffectPainter = new QPainter();
100 mEffectPainter->begin( mTempPicture );
101
102 context.setPainter( mEffectPainter );
103}
104
106{
107 if ( !mEffectPainter )
108 return;
109
110 mEffectPainter->end();
111 delete mEffectPainter;
112 mEffectPainter = nullptr;
113
114 //restore previous painter for context
115 context.setPainter( mPrevPainter );
116 mPrevPainter = nullptr;
117
118 // clear any existing pen/brush - sometimes these are not correctly restored when restoring a painter
119 // with a QPicture destination - see #15696
120 context.painter()->setPen( Qt::NoPen );
121 context.painter()->setBrush( Qt::NoBrush );
122
123 //draw using effect
124 render( *mTempPicture, context );
125
126 //clean up
127 delete mTempPicture;
128 mTempPicture = nullptr;
129}
130
131void QgsPaintEffect::drawSource( QPainter &painter )
132{
134 {
135 const QgsScopedQPainterState painterState( &painter );
136 fixQPictureDpi( &painter );
137 painter.drawPicture( 0, 0, *mPicture );
138 }
139 else
140 {
141 painter.drawPicture( 0, 0, *mPicture );
142 }
143}
144
146{
147 //have we already created a source image? if so, return it
148 if ( mSourceImage )
149 {
150 return mSourceImage;
151 }
152
153 if ( !mPicture )
154 return nullptr;
155
156 //else create it
157 //TODO - test with premultiplied image for speed
158 const QRectF bounds = imageBoundingRect( context );
159 mSourceImage = new QImage( bounds.width(), bounds.height(), QImage::Format_ARGB32 );
160 mSourceImage->fill( Qt::transparent );
161 QPainter imagePainter( mSourceImage );
162 imagePainter.setRenderHint( QPainter::Antialiasing );
163 imagePainter.translate( -bounds.left(), -bounds.top() );
164 imagePainter.drawPicture( 0, 0, *mPicture );
165 imagePainter.end();
166 mOwnsImage = true;
167 return mSourceImage;
168}
169
170QPointF QgsPaintEffect::imageOffset( const QgsRenderContext &context ) const
171{
172 return imageBoundingRect( context ).topLeft();
173}
174
175QRectF QgsPaintEffect::boundingRect( const QRectF &rect, const QgsRenderContext &context ) const
176{
177 Q_UNUSED( context )
178 return rect;
179}
180
181void QgsPaintEffect::fixQPictureDpi( QPainter *painter ) const
182{
183 // QPicture makes an assumption that we drawing to it with system DPI.
184 // Then when being drawn, it scales the painter. The following call
185 // negates the effect. There is no way of setting QPicture's DPI.
186 // See QTBUG-20361
187 painter->scale( static_cast< double >( qt_defaultDpiX() ) / painter->device()->logicalDpiX(),
188 static_cast< double >( qt_defaultDpiY() ) / painter->device()->logicalDpiY() );
189}
190
191QRectF QgsPaintEffect::imageBoundingRect( const QgsRenderContext &context ) const
192{
193 return boundingRect( mPicture->boundingRect(), context );
194}
195
196
197//
198// QgsDrawSourceEffect
199//
200
202{
204 effect->readProperties( map );
205 return effect;
206}
207
209{
210 if ( !enabled() || !context.painter() )
211 return;
212
213 QPainter *painter = context.painter();
214
215 if ( mBlendMode == QPainter::CompositionMode_SourceOver && qgsDoubleNear( mOpacity, 1.0 ) )
216 {
217 //just draw unmodified source
218 drawSource( *painter );
219 }
220 else
221 {
222 //rasterize source and apply modifications
223 QImage image = sourceAsImage( context )->copy();
224 QgsImageOperation::multiplyOpacity( image, mOpacity, context.feedback() );
225 const QgsScopedQPainterState painterState( painter );
226 painter->setCompositionMode( mBlendMode );
227 painter->drawImage( imageOffset( context ), image );
228 }
229}
230
232{
233 return new QgsDrawSourceEffect( *this );
234}
235
237{
238 QVariantMap props;
239 props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
240 props.insert( QStringLiteral( "draw_mode" ), QString::number( int( mDrawMode ) ) );
241 props.insert( QStringLiteral( "blend_mode" ), QString::number( int( mBlendMode ) ) );
242 props.insert( QStringLiteral( "opacity" ), QString::number( mOpacity ) );
243 return props;
244}
245
246void QgsDrawSourceEffect::readProperties( const QVariantMap &props )
247{
248 bool ok;
249 const QPainter::CompositionMode mode = static_cast< QPainter::CompositionMode >( props.value( QStringLiteral( "blend_mode" ) ).toInt( &ok ) );
250 if ( ok )
251 {
252 mBlendMode = mode;
253 }
254 if ( props.contains( QStringLiteral( "transparency" ) ) )
255 {
256 const double transparency = props.value( QStringLiteral( "transparency" ) ).toDouble( &ok );
257 if ( ok )
258 {
259 mOpacity = 1.0 - transparency;
260 }
261 }
262 else
263 {
264 const double opacity = props.value( QStringLiteral( "opacity" ) ).toDouble( &ok );
265 if ( ok )
266 {
267 mOpacity = opacity;
268 }
269 }
270 mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt();
271 mDrawMode = static_cast< QgsPaintEffect::DrawMode >( props.value( QStringLiteral( "draw_mode" ), QStringLiteral( "2" ) ).toInt() );
272}
273
274
275//
276// QgsEffectPainter
277//
278
280 : mRenderContext( renderContext )
281
282{
283 mPainter = renderContext.painter();
284 mPainter->save();
285}
286
288 : mRenderContext( renderContext )
289 , mEffect( effect )
290{
291 mPainter = mRenderContext.painter();
292 mPainter->save();
293 mEffect->begin( mRenderContext );
294}
295
297{
298 Q_ASSERT( !mEffect );
299
300 mEffect = effect;
301 mEffect->begin( mRenderContext );
302}
303
305{
306 Q_ASSERT( mEffect );
307
308 mEffect->end( mRenderContext );
309 mPainter->restore();
310}
A paint effect which draws the source picture with minor or no alterations.
QVariantMap properties() const override
Returns the properties describing the paint effect encoded in a string format.
QgsDrawSourceEffect()=default
Constructor for QgsDrawSourceEffect.
double opacity() const
Returns the opacity for the effect.
void draw(QgsRenderContext &context) override
Handles drawing of the effect's result on to the specified render context.
QgsDrawSourceEffect * clone() const override
Duplicates an effect by creating a deep copy of the effect.
static QgsPaintEffect * create(const QVariantMap &map)
Creates a new QgsDrawSource effect from a properties string map.
void readProperties(const QVariantMap &props) override
Reads a string map of an effect's properties and restores the effect to the state described by the pr...
QgsEffectPainter(QgsRenderContext &renderContext)
QgsEffectPainter constructor.
void setEffect(QgsPaintEffect *effect)
Sets the effect to be painted.
static void multiplyOpacity(QImage &image, double factor, QgsFeedback *feedback=nullptr)
Multiplies opacity of image pixel values by a factor.
Base class for visual effects which can be applied to QPicture drawings.
QgsPaintEffect()=default
Constructor for QgsPaintEffect.
bool requiresQPainterDpiFix
DrawMode mDrawMode
void setDrawMode(DrawMode drawMode)
Sets the draw mode for the effect.
void setEnabled(bool enabled)
Sets whether the effect is enabled.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
void drawSource(QPainter &painter)
Draws the source QPicture onto the specified painter.
virtual void readProperties(const QVariantMap &props)=0
Reads a string map of an effect's properties and restores the effect to the state described by the pr...
virtual void begin(QgsRenderContext &context)
Begins intercepting paint operations to a render context.
QPointF imageOffset(const QgsRenderContext &context) const
Returns the offset which should be used when drawing the source image on to a destination render cont...
virtual void render(QPicture &picture, QgsRenderContext &context)
Renders a picture using the effect.
DrawMode drawMode() const
Returns the draw mode for the effect.
virtual ~QgsPaintEffect()
virtual void end(QgsRenderContext &context)
Ends interception of paint operations to a render context, and draws the result to the render context...
bool enabled() const
Returns whether the effect is enabled.
virtual QRectF boundingRect(const QRectF &rect, const QgsRenderContext &context) const
Returns the bounding rect required for drawing the effect.
virtual QVariantMap properties() const =0
Returns the properties describing the paint effect encoded in a string format.
void fixQPictureDpi(QPainter *painter) const
Applies a workaround to a QPainter to avoid an issue with incorrect scaling when drawing QPictures.
DrawMode
Drawing modes for effects.
virtual void draw(QgsRenderContext &context)=0
Handles drawing of the effect's result on to the specified render context.
virtual QString type() const =0
Returns the effect type.
QImage * sourceAsImage(QgsRenderContext &context)
Returns the source QPicture rendered to a new QImage.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
static void saveProperties(QVariantMap props, QDomDocument &doc, QDomElement &element)
Saves the map of properties to XML.
static QVariantMap parseProperties(const QDomElement &element)
Parses the properties from XML and returns a map.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2507
Q_GUI_EXPORT int qt_defaultDpiX()
Q_GUI_EXPORT int qt_defaultDpiY()