QGIS API Documentation  3.0.2-Girona (307d082)
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 }
void setEnabled(const bool enabled)
Sets whether the effect is enabled.
virtual QgsStringMap properties() const =0
Returns the properties describing the paint effect encoded in a string format.
DrawMode mDrawMode
static void multiplyOpacity(QImage &image, const double factor)
Multiplies opacity of image pixel values by a factor.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
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.
void setDrawMode(const DrawMode drawMode)
Sets the draw mode for the effect.
Q_GUI_EXPORT int qt_defaultDpiX()
DrawMode drawMode() const
Returns the draw mode for the effect.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:479
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
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.
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.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.