1 /***************************************************************************
2                              qgspainteffect.h
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 #ifndef QGSPAINTEFFECT_H
18 #define QGSPAINTEFFECT_H
19 
20 #include "qgis_core.h"
21 #include "qgis_sip.h"
22 #include "qgssymbollayer.h"
23 #include <QPainter>
24 #include <QDomDocument>
25 #include <QDomElement>
26 
27 class QgsRenderContext;
28 
29 /**
30  * \ingroup core
31  * \class QgsPaintEffect
32  * \brief Base class for visual effects which can be applied to QPicture drawings
33  *
34  * QgsPaintEffect objects can be used to modify QPicture drawings prior to rendering
35  * them with a QPainter operation. There are two methods for drawing using an effect,
36  * either drawing a picture directly, or by intercepting drawing operations to a
37  * render context.
38  *
39  * To directly draw a picture, use the render() method with a source
40  * QPicture and destination render context.
41  *
42  * Intercepting drawing operations to a render context is achieved by first calling
43  * the begin() method, passing a render context. Any drawing operations
44  * performed on the render context will not directly affect the context's paint
45  * device. When the drawing operations have been completed, call the end()
46  * method. This will perform the paint effect on the intercepted drawing operations
47  * and render the result to the render context's paint device.
48  *
49  * \see QgsPaintEffectRegistry
50  * \since QGIS 2.9
51  */
52 
53 class CORE_EXPORT QgsPaintEffect SIP_NODEFAULTCTORS
54 {
55 
56 #ifdef SIP_RUN
57     SIP_CONVERT_TO_SUBCLASS_CODE
58     if ( sipCpp->type() == "drawSource" && dynamic_cast<QgsDrawSourceEffect *>( sipCpp ) != NULL )
59     {
60       sipType = sipType_QgsDrawSourceEffect;
61     }
62     else if ( sipCpp->type() == "effectStack" && dynamic_cast<QgsEffectStack *>( sipCpp ) != NULL )
63     {
64       sipType = sipType_QgsEffectStack;
65     }
66     else if ( sipCpp->type() == "blur" && dynamic_cast<QgsBlurEffect *>( sipCpp ) != NULL )
67     {
68       sipType = sipType_QgsBlurEffect;
69     }
70     else if ( sipCpp->type() == "dropShadow" && dynamic_cast<QgsDropShadowEffect *>( sipCpp ) != NULL )
71     {
72       sipType = sipType_QgsDropShadowEffect;
73     }
74     else if ( sipCpp->type() == "outerGlow" && dynamic_cast<QgsOuterGlowEffect *>( sipCpp ) != NULL )
75     {
76       sipType = sipType_QgsOuterGlowEffect;
77     }
78     else if ( sipCpp->type() == "innerGlow" && dynamic_cast<QgsInnerGlowEffect *>( sipCpp ) != NULL )
79     {
80       sipType = sipType_QgsInnerGlowEffect;
81     }
82     else if ( sipCpp->type() == "transform" && dynamic_cast<QgsTransformEffect *>( sipCpp ) != NULL )
83     {
84       sipType = sipType_QgsTransformEffect;
85     }
86     else if ( sipCpp->type() == "color" && dynamic_cast<QgsColorEffect *>( sipCpp ) != NULL )
87     {
88       sipType = sipType_QgsColorEffect;
89     }
90     else
91     {
92       sipType = 0;
93     }
94     SIP_END
95 #endif
96 
97   public:
98 
99     /**
100      * Drawing modes for effects. These modes are used only when effects are
101      * drawn as part of an effects stack
102      * \see QgsEffectStack
103      */
104     enum DrawMode
105     {
106       Modifier, //!< The result of the effect is not rendered, but is passed on to following effects in the stack
107       Render, //!< The result of the effect is rendered on the destination, but does not affect subsequent effects in the stack
108       ModifyAndRender //!< The result of the effect is both rendered and passed on to subsequent effects in the stack
109     };
110 
111     /**
112      * Constructor for QgsPaintEffect.
113      */
114     QgsPaintEffect() = default;
115 
116     QgsPaintEffect( const QgsPaintEffect &other );
117     virtual ~QgsPaintEffect();
118 
119     /**
120      * Returns the effect type.
121      * \returns unique string representation of the effect type
122      */
123     virtual QString type() const = 0;
124 
125     /**
126      * Duplicates an effect by creating a deep copy of the effect
127      * \returns clone of paint effect
128      */
129     virtual QgsPaintEffect *clone() const = 0 SIP_FACTORY;
130 
131     /**
132      * Returns the properties describing the paint effect encoded in a
133      * string format.
134      * \returns string map of properties, in the form property key, value
135      * \see readProperties
136      * \see saveProperties
137      */
138     virtual QgsStringMap properties() const = 0;
139 
140     /**
141      * Reads a string map of an effect's properties and restores the effect
142      * to the state described by the properties map.
143      * \param props effect properties encoded in a string map
144      * \see properties
145      */
146     virtual void readProperties( const QgsStringMap &props ) = 0;
147 
148     /**
149      * Saves the current state of the effect to a DOM element. The default
150      * behavior is to save the properties string map returned by
151      * properties().
152      * \param doc destination DOM document
153      * \param element destination DOM element
154      * \returns TRUE if save was successful
155      * \see readProperties
156      */
157     virtual bool saveProperties( QDomDocument &doc, QDomElement &element ) const;
158 
159     /**
160      * Restores the effect to the state described by a DOM element.
161      * \param element DOM element describing an effect's state
162      * \returns TRUE if read was successful
163      * \see saveProperties
164      */
165     virtual bool readProperties( const QDomElement &element );
166 
167     /**
168      * Renders a picture using the effect.
169      * \param picture source QPicture to render
170      * \param context destination render context
171      * \see begin
172      */
173     virtual void render( QPicture &picture, QgsRenderContext &context );
174 
175     /**
176      * Begins intercepting paint operations to a render context. When the corresponding
177      * end() member is called all intercepted paint operations will be
178      * drawn to the render context after being modified by the effect.
179      * \param context destination render context
180      * \see end
181      * \see render
182      */
183     virtual void begin( QgsRenderContext &context );
184 
185     /**
186      * Ends interception of paint operations to a render context, and draws the result
187      * to the render context after being modified by the effect.
188      * \param context destination render context
189      * \see begin
190      */
191     virtual void end( QgsRenderContext &context );
192 
193     /**
194      * Returns whether the effect is enabled
195      * \returns TRUE if effect is enabled
196      * \see setEnabled
197      */
enabled()198     bool enabled() const { return mEnabled; }
199 
200     /**
201      * Sets whether the effect is enabled
202      * \param enabled set to FALSE to disable the effect
203      * \see enabled
204      */
205     void setEnabled( bool enabled );
206 
207     /**
208      * Returns the draw mode for the effect. This property only has an
209      * effect if the paint effect is used in a QgsEffectStack.
210      * \returns draw mode for effect
211      * \see setDrawMode
212      */
drawMode()213     DrawMode drawMode() const { return mDrawMode; }
214 
215     /**
216      * Sets the draw mode for the effect. This property only has an
217      * effect if the paint effect is used in a QgsEffectStack.
218      * \param drawMode draw mode for effect
219      * \see drawMode
220      */
221     void setDrawMode( DrawMode drawMode );
222 
223   protected:
224 
225     bool mEnabled = true;
226     DrawMode mDrawMode = ModifyAndRender;
227     bool requiresQPainterDpiFix = true;
228 
229     /**
230      * Handles drawing of the effect's result on to the specified render context.
231      * Derived classes must reimplement this method to apply any transformations to
232      * the source QPicture and draw the result using the context's painter.
233      * \param context destination render context
234      * \see drawSource
235      */
236     virtual void draw( QgsRenderContext &context ) = 0;
237 
238     /**
239      * Draws the source QPicture onto the specified painter. Handles scaling of the picture
240      * to account for the destination painter's DPI.
241      * \param painter destination painter
242      * \see source
243      * \see sourceAsImage
244      */
245     void drawSource( QPainter &painter );
246 
247     /**
248      * Returns the source QPicture. The draw() member can utilize this when
249      * drawing the effect.
250      * \returns source QPicture
251      * \see drawSource
252      * \see sourceAsImage
253      */
source()254     const QPicture *source() const { return mPicture; }
255 
256     /**
257      * Returns the source QPicture rendered to a new QImage. The draw() member can
258      * utilize this when drawing the effect. The image will be padded or cropped from the original
259      * source QPicture by the results of the boundingRect() method.
260      * The result is cached to speed up subsequent calls to sourceAsImage.
261      * \returns source QPicture rendered to an image
262      * \see drawSource
263      * \see source
264      * \see imageOffset
265      * \see boundingRect
266      */
267     QImage *sourceAsImage( QgsRenderContext &context );
268 
269     /**
270      * Returns the offset which should be used when drawing the source image on to a destination
271      * render context.
272      * \param context destination render context
273      * \returns point offset for image top left corner
274      * \see sourceAsImage
275      */
276     QPointF imageOffset( const QgsRenderContext &context ) const;
277 
278     /**
279      * Returns the bounding rect required for drawing the effect. This method can be used
280      * to expand the bounding rect of a source picture to account for offset or blurring
281      * effects.
282      * \param rect original source bounding rect
283      * \param context destination render context
284      * \returns modified bounding rect
285      * \see sourceAsImage
286      */
287     virtual QRectF boundingRect( const QRectF &rect, const QgsRenderContext &context ) const;
288 
289     /**
290      * Applies a workaround to a QPainter to avoid an issue with incorrect scaling
291      * when drawing QPictures. This may need to be called by derived classes prior
292      * to rendering results onto a painter.
293      * \param painter destination painter
294      */
295     void fixQPictureDpi( QPainter *painter ) const;
296 
297   private:
298 
299     const QPicture *mPicture = nullptr;
300     QImage *mSourceImage = nullptr;
301     bool mOwnsImage = false;
302 
303     QPainter *mPrevPainter = nullptr;
304     QPainter *mEffectPainter = nullptr;
305     QPicture *mTempPicture = nullptr;
306 
307     QRectF imageBoundingRect( const QgsRenderContext &context ) const;
308 
309     friend class QgsEffectStack;
310 
311     QgsPaintEffect &operator= ( const QgsPaintEffect & ) = delete;
312 
313 };
314 
315 /**
316  * \ingroup core
317  * \class QgsDrawSourceEffect
318  * \brief A paint effect which draws the source picture with minor or no alterations
319  *
320  * The draw source effect can be used to draw an unaltered copy of the original source
321  * picture. Minor changes like lowering the opacity and applying a blend mode are
322  * supported, however these changes will force the resultant output to be rasterized.
323  * If no alterations are performed then the original picture will be rendered as a vector.
324  *
325  * \since QGIS 2.9
326  */
327 
328 class CORE_EXPORT QgsDrawSourceEffect : public QgsPaintEffect SIP_NODEFAULTCTORS
329 {
330   public:
331 
332     //! Constructor for QgsDrawSourceEffect
333     QgsDrawSourceEffect() = default;
334 
335     /**
336      * Creates a new QgsDrawSource effect from a properties string map.
337      * \param map encoded properties string map
338      * \returns new QgsDrawSourceEffect
339      */
340     static QgsPaintEffect *create( const QgsStringMap &map ) SIP_FACTORY;
341 
type()342     QString type() const override { return QStringLiteral( "drawSource" ); }
343     QgsDrawSourceEffect *clone() const override SIP_FACTORY;
344     QgsStringMap properties() const override;
345     void readProperties( const QgsStringMap &props ) override;
346 
347     /**
348      * Sets the \a opacity for the effect.
349      * \param opacity double between 0 and 1 inclusive, where 0 is fully transparent
350      * and 1 is fully opaque
351      * \see opacity()
352      */
setOpacity(const double opacity)353     void setOpacity( const double opacity ) { mOpacity = opacity; }
354 
355     /**
356      * Returns the opacity for the effect
357      * \returns opacity value between 0 and 1 inclusive, where 0 is fully transparent
358      * and 1 is fully opaque
359      * \see setOpacity()
360      */
opacity()361     double opacity() const { return mOpacity; }
362 
363     /**
364      * Sets the blend mode for the effect
365      * \param mode blend mode used for drawing the source on to a destination
366      * paint device
367      * \see blendMode
368      */
setBlendMode(const QPainter::CompositionMode mode)369     void setBlendMode( const QPainter::CompositionMode mode ) { mBlendMode = mode; }
370 
371     /**
372      * Returns the blend mode for the effect
373      * \returns blend mode used for drawing the source on to a destination
374      * paint device
375      * \see setBlendMode
376      */
blendMode()377     QPainter::CompositionMode blendMode() const { return mBlendMode; }
378 
379   protected:
380 
381     void draw( QgsRenderContext &context ) override;
382 
383   private:
384 
385     double mOpacity = 1.0;
386     QPainter::CompositionMode mBlendMode = QPainter::CompositionMode_SourceOver;
387 };
388 
389 /**
390  * \ingroup core
391  * \class QgsEffectPainter
392  * \brief A class to manager painter saving and restoring required for effect drawing
393  *
394  * \since QGIS 3.0
395  */
396 class CORE_EXPORT QgsEffectPainter
397 {
398   public:
399 
400     /**
401      * QgsEffectPainter constructor
402      *
403      * \param renderContext the QgsRenderContext object
404      * \since QGIS 3.0
405      */
406     QgsEffectPainter( QgsRenderContext &renderContext );
407 
408     /**
409      * QgsEffectPainter constructor alternative if no painter translation is needed
410      *
411      * \param renderContext the QgsRenderContext object
412      * \param effect the QgsPaintEffect object
413      * \since QGIS 3.0
414      */
415     QgsEffectPainter( QgsRenderContext &renderContext, QgsPaintEffect *effect );
416     ~QgsEffectPainter();
417 
418     /**
419      * Sets the effect to be painted
420      *
421      * \param effect the QgsPaintEffect object
422      */
423     void setEffect( QgsPaintEffect *effect );
424 
425     ///@cond PRIVATE
426 
427     /**
428      * Access to the painter object
429      *
430      * \since QGIS 3.0
431      */
432     QPainter *operator->() { return mPainter; }
433     ///@endcond
434 
435   private:
436 #ifdef SIP_RUN
437     const QgsEffectPainter &operator=( const QgsEffectPainter & );
438 #endif
439 
440     QgsRenderContext &mRenderContext;
441     QPainter *mPainter = nullptr;
442     QgsPaintEffect *mEffect = nullptr;
443 };
444 
445 #endif // QGSPAINTEFFECT_H
446 
447