QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
25 Q_GUI_EXPORT extern int qt_defaultDpiX();
26 Q_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 
45 void QgsPaintEffect::setEnabled( const bool enabled )
46 {
47  mEnabled = enabled;
48 }
49 
51 {
53 }
54 
55 bool 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 
68 bool QgsPaintEffect::readProperties( const QDomElement &element )
69 {
70  if ( element.isNull() )
71  {
72  return false;
73  }
74 
75  QVariantMap props = QgsSymbolLayerUtils::parseProperties( element );
76  readProperties( props );
77  return true;
78 }
79 
80 void 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 
131 void QgsPaintEffect::drawSource( QPainter &painter )
132 {
134  {
135  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  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 
170 QPointF QgsPaintEffect::imageOffset( const QgsRenderContext &context ) const
171 {
172  return imageBoundingRect( context ).topLeft();
173 }
174 
175 QRectF QgsPaintEffect::boundingRect( const QRectF &rect, const QgsRenderContext &context ) const
176 {
177  Q_UNUSED( context )
178  return rect;
179 }
180 
181 void 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 
191 QRectF QgsPaintEffect::imageBoundingRect( const QgsRenderContext &context ) const
192 {
193  return boundingRect( mPicture->boundingRect(), context );
194 }
195 
196 
197 //
198 // QgsDrawSourceEffect
199 //
200 
201 QgsPaintEffect *QgsDrawSourceEffect::create( const QVariantMap &map )
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 );
225  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 
246 void QgsDrawSourceEffect::readProperties( const QVariantMap &props )
247 {
248  bool ok;
249  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  double transparency = props.value( QStringLiteral( "transparency" ) ).toDouble( &ok );
257  if ( ok )
258  {
259  mOpacity = 1.0 - transparency;
260  }
261  }
262  else
263  {
264  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)
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.
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:598
Q_GUI_EXPORT int qt_defaultDpiX()
Q_GUI_EXPORT int qt_defaultDpiY()