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