QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 <QPicture>
23 
24 Q_GUI_EXPORT extern int qt_defaultDpiX();
25 Q_GUI_EXPORT extern int qt_defaultDpiY();
26 
28  : mEnabled( other.enabled() )
29  , mDrawMode( other.drawMode() )
30 {
31 
32 }
33 
35 {
36  if ( mOwnsImage )
37  {
38  delete mSourceImage;
39  }
40  delete mEffectPainter;
41  delete mTempPicture;
42 }
43 
45 {
46  mEnabled = enabled;
47 }
48 
50 {
52 }
53 
54 bool QgsPaintEffect::saveProperties( QDomDocument &doc, QDomElement &element ) const
55 {
56  if ( element.isNull() )
57  {
58  return false;
59  }
60 
61  QDomElement effectElement = doc.createElement( QStringLiteral( "effect" ) );
62  effectElement.setAttribute( QStringLiteral( "type" ), type() );
63 
64  QgsStringMap props = properties();
65  for ( QgsStringMap::iterator it = props.begin(); it != props.end(); ++it )
66  {
67  QDomElement propEl = doc.createElement( QStringLiteral( "prop" ) );
68  propEl.setAttribute( QStringLiteral( "k" ), it.key() );
69  propEl.setAttribute( QStringLiteral( "v" ), it.value() );
70  effectElement.appendChild( propEl );
71  }
72 
73  element.appendChild( effectElement );
74  return true;
75 }
76 
77 bool QgsPaintEffect::readProperties( const QDomElement &element )
78 {
79  if ( element.isNull() )
80  {
81  return false;
82  }
83 
84  //default implementation converts to a string map
85  QgsStringMap props;
86 
87  QDomElement e = element.firstChildElement();
88  while ( !e.isNull() )
89  {
90  if ( e.tagName() != QLatin1String( "prop" ) )
91  {
92  QgsDebugMsg( "unknown tag " + e.tagName() );
93  }
94  else
95  {
96  QString propKey = e.attribute( QStringLiteral( "k" ) );
97  QString propValue = e.attribute( QStringLiteral( "v" ) );
98  props[propKey] = propValue;
99  }
100  e = e.nextSiblingElement();
101  }
102 
103  readProperties( props );
104  return true;
105 }
106 
107 void QgsPaintEffect::render( QPicture &picture, QgsRenderContext &context )
108 {
109  //set source picture
110  mPicture = &picture;
111  delete mSourceImage;
112  mSourceImage = nullptr;
113 
114  draw( context );
115 }
116 
118 {
119  //temporarily replace painter and direct paint operations for context to a QPicture
120  mPrevPainter = context.painter();
121 
122  delete mTempPicture;
123  mTempPicture = new QPicture();
124 
125  delete mEffectPainter;
126  mEffectPainter = new QPainter();
127  mEffectPainter->begin( mTempPicture );
128 
129  context.setPainter( mEffectPainter );
130 }
131 
133 {
134  if ( !mEffectPainter )
135  return;
136 
137  mEffectPainter->end();
138  delete mEffectPainter;
139  mEffectPainter = nullptr;
140 
141  //restore previous painter for context
142  context.setPainter( mPrevPainter );
143  mPrevPainter = nullptr;
144 
145  // clear any existing pen/brush - sometimes these are not correctly restored when restoring a painter
146  // with a QPicture destination - see #15696
147  context.painter()->setPen( Qt::NoPen );
148  context.painter()->setBrush( Qt::NoBrush );
149 
150  //draw using effect
151  render( *mTempPicture, context );
152 
153  //clean up
154  delete mTempPicture;
155  mTempPicture = nullptr;
156 }
157 
158 void QgsPaintEffect::drawSource( QPainter &painter )
159 {
161  {
162  painter.save();
163  fixQPictureDpi( &painter );
164  painter.drawPicture( 0, 0, *mPicture );
165  painter.restore();
166  }
167  else
168  {
169  painter.drawPicture( 0, 0, *mPicture );
170  }
171 }
172 
174 {
175  //have we already created a source image? if so, return it
176  if ( mSourceImage )
177  {
178  return mSourceImage;
179  }
180 
181  if ( !mPicture )
182  return nullptr;
183 
184  //else create it
185  //TODO - test with premultiplied image for speed
186  QRectF bounds = imageBoundingRect( context );
187  mSourceImage = new QImage( bounds.width(), bounds.height(), QImage::Format_ARGB32 );
188  mSourceImage->fill( Qt::transparent );
189  QPainter imagePainter( mSourceImage );
190  imagePainter.setRenderHint( QPainter::Antialiasing );
191  imagePainter.translate( -bounds.left(), -bounds.top() );
192  imagePainter.drawPicture( 0, 0, *mPicture );
193  imagePainter.end();
194  mOwnsImage = true;
195  return mSourceImage;
196 }
197 
198 QPointF QgsPaintEffect::imageOffset( const QgsRenderContext &context ) const
199 {
200  return imageBoundingRect( context ).topLeft();
201 }
202 
203 QRectF QgsPaintEffect::boundingRect( const QRectF &rect, const QgsRenderContext &context ) const
204 {
205  Q_UNUSED( context )
206  return rect;
207 }
208 
209 void QgsPaintEffect::fixQPictureDpi( QPainter *painter ) const
210 {
211  // QPicture makes an assumption that we drawing to it with system DPI.
212  // Then when being drawn, it scales the painter. The following call
213  // negates the effect. There is no way of setting QPicture's DPI.
214  // See QTBUG-20361
215  painter->scale( static_cast< double >( qt_defaultDpiX() ) / painter->device()->logicalDpiX(),
216  static_cast< double >( qt_defaultDpiY() ) / painter->device()->logicalDpiY() );
217 }
218 
219 QRectF QgsPaintEffect::imageBoundingRect( const QgsRenderContext &context ) const
220 {
221  return boundingRect( mPicture->boundingRect(), context );
222 }
223 
224 
225 //
226 // QgsDrawSourceEffect
227 //
228 
230 {
232  effect->readProperties( map );
233  return effect;
234 }
235 
237 {
238  if ( !enabled() || !context.painter() )
239  return;
240 
241  QPainter *painter = context.painter();
242 
243  if ( mBlendMode == QPainter::CompositionMode_SourceOver && qgsDoubleNear( mOpacity, 1.0 ) )
244  {
245  //just draw unmodified source
246  drawSource( *painter );
247  }
248  else
249  {
250  //rasterize source and apply modifications
251  QImage image = sourceAsImage( context )->copy();
252  QgsImageOperation::multiplyOpacity( image, mOpacity );
253  painter->save();
254  painter->setCompositionMode( mBlendMode );
255  painter->drawImage( imageOffset( context ), image );
256  painter->restore();
257  }
258 }
259 
261 {
262  return new QgsDrawSourceEffect( *this );
263 }
264 
266 {
267  QgsStringMap props;
268  props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
269  props.insert( QStringLiteral( "draw_mode" ), QString::number( int( mDrawMode ) ) );
270  props.insert( QStringLiteral( "blend_mode" ), QString::number( int( mBlendMode ) ) );
271  props.insert( QStringLiteral( "opacity" ), QString::number( mOpacity ) );
272  return props;
273 }
274 
276 {
277  bool ok;
278  QPainter::CompositionMode mode = static_cast< QPainter::CompositionMode >( props.value( QStringLiteral( "blend_mode" ) ).toInt( &ok ) );
279  if ( ok )
280  {
281  mBlendMode = mode;
282  }
283  if ( props.contains( QStringLiteral( "transparency" ) ) )
284  {
285  double transparency = props.value( QStringLiteral( "transparency" ) ).toDouble( &ok );
286  if ( ok )
287  {
288  mOpacity = 1.0 - transparency;
289  }
290  }
291  else
292  {
293  double opacity = props.value( QStringLiteral( "opacity" ) ).toDouble( &ok );
294  if ( ok )
295  {
296  mOpacity = opacity;
297  }
298  }
299  mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt();
300  mDrawMode = static_cast< QgsPaintEffect::DrawMode >( props.value( QStringLiteral( "draw_mode" ), QStringLiteral( "2" ) ).toInt() );
301 }
302 
303 
304 //
305 // QgsEffectPainter
306 //
307 
309  : mRenderContext( renderContext )
310 
311 {
312  mPainter = renderContext.painter();
313  mPainter->save();
314 }
315 
317  : mRenderContext( renderContext )
318  , mEffect( effect )
319 {
320  mPainter = mRenderContext.painter();
321  mPainter->save();
322  mEffect->begin( mRenderContext );
323 }
324 
326 {
327  Q_ASSERT( !mEffect );
328 
329  mEffect = effect;
330  mEffect->begin( mRenderContext );
331 }
332 
334 {
335  Q_ASSERT( mEffect );
336 
337  mEffect->end( mRenderContext );
338  mPainter->restore();
339 }
static void multiplyOpacity(QImage &image, double factor)
Multiplies opacity of image pixel values by a factor.
virtual QgsStringMap properties() const =0
Returns the properties describing the paint effect encoded in a string format.
DrawMode mDrawMode
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
void fixQPictureDpi(QPainter *painter) const
Applies a workaround to a QPainter to avoid an issue with incorrect scaling when drawing QPictures...
Base class for visual effects which can be applied to QPicture drawings.
Q_GUI_EXPORT int qt_defaultDpiX()
DrawMode drawMode() const
Returns the draw mode for the effect.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:587
QImage * sourceAsImage(QgsRenderContext &context)
Returns the source QPicture rendered to a new QImage.
QPointF imageOffset(const QgsRenderContext &context) const
Returns the offset which should be used when drawing the source image on to a destination render cont...
static QgsPaintEffect * create(const QgsStringMap &map)
Creates a new QgsDrawSource effect from a properties string map.
void setEnabled(bool enabled)
Sets whether the effect is enabled.
QgsDrawSourceEffect * clone() const override
Duplicates an effect by creating a deep copy of the effect.
virtual QString type() const =0
Returns the effect type.
QgsStringMap properties() const override
Returns the properties describing the paint effect encoded in a string format.
QgsEffectPainter(QgsRenderContext &renderContext)
QgsEffectPainter constructor.
virtual ~QgsPaintEffect()
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
Q_GUI_EXPORT int qt_defaultDpiY()
bool enabled() const
Returns whether the effect is enabled.
void readProperties(const QgsStringMap &props) override
Reads a string map of an effect&#39;s properties and restores the effect to the state described by the pr...
bool requiresQPainterDpiFix
void setEffect(QgsPaintEffect *effect)
Sets the effect to be painted.
DrawMode
Drawing modes for effects.
virtual void readProperties(const QgsStringMap &props)=0
Reads a string map of an effect&#39;s properties and restores the effect to the state described by the pr...
Contains information about the context of a rendering operation.
virtual QRectF boundingRect(const QRectF &rect, const QgsRenderContext &context) const
Returns the bounding rect required for drawing the effect.
QPainter * painter()
Returns the destination QPainter for the render operation.
void drawSource(QPainter &painter)
Draws the source QPicture onto the specified painter.
virtual void render(QPicture &picture, QgsRenderContext &context)
Renders a picture using the effect.
virtual void draw(QgsRenderContext &context)=0
Handles drawing of the effect&#39;s result on to the specified render context.
void draw(QgsRenderContext &context) override
Handles drawing of the effect&#39;s result on to the specified render context.
QgsPaintEffect()=default
Constructor for QgsPaintEffect.
A paint effect which draws the source picture with minor or no alterations.
virtual void end(QgsRenderContext &context)
Ends interception of paint operations to a render context, and draws the result to the render context...
virtual void begin(QgsRenderContext &context)
Begins intercepting paint operations to a render context.
void setDrawMode(DrawMode drawMode)
Sets the draw mode for the effect.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.