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
QgsPaintEffect(const QgsPaintEffect & other)27 QgsPaintEffect::QgsPaintEffect( const QgsPaintEffect &other )
28 : mEnabled( other.enabled() )
29 , mDrawMode( other.drawMode() )
30 {
31
32 }
33
~QgsPaintEffect()34 QgsPaintEffect::~QgsPaintEffect()
35 {
36 if ( mOwnsImage )
37 {
38 delete mSourceImage;
39 }
40 delete mEffectPainter;
41 delete mTempPicture;
42 }
43
setEnabled(const bool enabled)44 void QgsPaintEffect::setEnabled( const bool enabled )
45 {
46 mEnabled = enabled;
47 }
48
setDrawMode(const QgsPaintEffect::DrawMode drawMode)49 void QgsPaintEffect::setDrawMode( const QgsPaintEffect::DrawMode drawMode )
50 {
51 mDrawMode = drawMode;
52 }
53
saveProperties(QDomDocument & doc,QDomElement & element) const54 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
readProperties(const QDomElement & element)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
render(QPicture & picture,QgsRenderContext & context)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
begin(QgsRenderContext & context)117 void QgsPaintEffect::begin( QgsRenderContext &context )
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
end(QgsRenderContext & context)132 void QgsPaintEffect::end( QgsRenderContext &context )
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
drawSource(QPainter & painter)158 void QgsPaintEffect::drawSource( QPainter &painter )
159 {
160 if ( requiresQPainterDpiFix )
161 {
162 QgsScopedQPainterState painterState( &painter );
163 fixQPictureDpi( &painter );
164 painter.drawPicture( 0, 0, *mPicture );
165 }
166 else
167 {
168 painter.drawPicture( 0, 0, *mPicture );
169 }
170 }
171
sourceAsImage(QgsRenderContext & context)172 QImage *QgsPaintEffect::sourceAsImage( QgsRenderContext &context )
173 {
174 //have we already created a source image? if so, return it
175 if ( mSourceImage )
176 {
177 return mSourceImage;
178 }
179
180 if ( !mPicture )
181 return nullptr;
182
183 //else create it
184 //TODO - test with premultiplied image for speed
185 QRectF bounds = imageBoundingRect( context );
186 mSourceImage = new QImage( bounds.width(), bounds.height(), QImage::Format_ARGB32 );
187 mSourceImage->fill( Qt::transparent );
188 QPainter imagePainter( mSourceImage );
189 imagePainter.setRenderHint( QPainter::Antialiasing );
190 imagePainter.translate( -bounds.left(), -bounds.top() );
191 imagePainter.drawPicture( 0, 0, *mPicture );
192 imagePainter.end();
193 mOwnsImage = true;
194 return mSourceImage;
195 }
196
imageOffset(const QgsRenderContext & context) const197 QPointF QgsPaintEffect::imageOffset( const QgsRenderContext &context ) const
198 {
199 return imageBoundingRect( context ).topLeft();
200 }
201
boundingRect(const QRectF & rect,const QgsRenderContext & context) const202 QRectF QgsPaintEffect::boundingRect( const QRectF &rect, const QgsRenderContext &context ) const
203 {
204 Q_UNUSED( context )
205 return rect;
206 }
207
fixQPictureDpi(QPainter * painter) const208 void QgsPaintEffect::fixQPictureDpi( QPainter *painter ) const
209 {
210 // QPicture makes an assumption that we drawing to it with system DPI.
211 // Then when being drawn, it scales the painter. The following call
212 // negates the effect. There is no way of setting QPicture's DPI.
213 // See QTBUG-20361
214 painter->scale( static_cast< double >( qt_defaultDpiX() ) / painter->device()->logicalDpiX(),
215 static_cast< double >( qt_defaultDpiY() ) / painter->device()->logicalDpiY() );
216 }
217
imageBoundingRect(const QgsRenderContext & context) const218 QRectF QgsPaintEffect::imageBoundingRect( const QgsRenderContext &context ) const
219 {
220 return boundingRect( mPicture->boundingRect(), context );
221 }
222
223
224 //
225 // QgsDrawSourceEffect
226 //
227
create(const QgsStringMap & map)228 QgsPaintEffect *QgsDrawSourceEffect::create( const QgsStringMap &map )
229 {
230 QgsDrawSourceEffect *effect = new QgsDrawSourceEffect();
231 effect->readProperties( map );
232 return effect;
233 }
234
draw(QgsRenderContext & context)235 void QgsDrawSourceEffect::draw( QgsRenderContext &context )
236 {
237 if ( !enabled() || !context.painter() )
238 return;
239
240 QPainter *painter = context.painter();
241
242 if ( mBlendMode == QPainter::CompositionMode_SourceOver && qgsDoubleNear( mOpacity, 1.0 ) )
243 {
244 //just draw unmodified source
245 drawSource( *painter );
246 }
247 else
248 {
249 //rasterize source and apply modifications
250 QImage image = sourceAsImage( context )->copy();
251 QgsImageOperation::multiplyOpacity( image, mOpacity );
252 QgsScopedQPainterState painterState( painter );
253 painter->setCompositionMode( mBlendMode );
254 painter->drawImage( imageOffset( context ), image );
255 }
256 }
257
clone() const258 QgsDrawSourceEffect *QgsDrawSourceEffect::clone() const
259 {
260 return new QgsDrawSourceEffect( *this );
261 }
262
properties() const263 QgsStringMap QgsDrawSourceEffect::properties() const
264 {
265 QgsStringMap props;
266 props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
267 props.insert( QStringLiteral( "draw_mode" ), QString::number( int( mDrawMode ) ) );
268 props.insert( QStringLiteral( "blend_mode" ), QString::number( int( mBlendMode ) ) );
269 props.insert( QStringLiteral( "opacity" ), QString::number( mOpacity ) );
270 return props;
271 }
272
readProperties(const QgsStringMap & props)273 void QgsDrawSourceEffect::readProperties( const QgsStringMap &props )
274 {
275 bool ok;
276 QPainter::CompositionMode mode = static_cast< QPainter::CompositionMode >( props.value( QStringLiteral( "blend_mode" ) ).toInt( &ok ) );
277 if ( ok )
278 {
279 mBlendMode = mode;
280 }
281 if ( props.contains( QStringLiteral( "transparency" ) ) )
282 {
283 double transparency = props.value( QStringLiteral( "transparency" ) ).toDouble( &ok );
284 if ( ok )
285 {
286 mOpacity = 1.0 - transparency;
287 }
288 }
289 else
290 {
291 double opacity = props.value( QStringLiteral( "opacity" ) ).toDouble( &ok );
292 if ( ok )
293 {
294 mOpacity = opacity;
295 }
296 }
297 mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt();
298 mDrawMode = static_cast< QgsPaintEffect::DrawMode >( props.value( QStringLiteral( "draw_mode" ), QStringLiteral( "2" ) ).toInt() );
299 }
300
301
302 //
303 // QgsEffectPainter
304 //
305
QgsEffectPainter(QgsRenderContext & renderContext)306 QgsEffectPainter::QgsEffectPainter( QgsRenderContext &renderContext )
307 : mRenderContext( renderContext )
308
309 {
310 mPainter = renderContext.painter();
311 mPainter->save();
312 }
313
QgsEffectPainter(QgsRenderContext & renderContext,QgsPaintEffect * effect)314 QgsEffectPainter::QgsEffectPainter( QgsRenderContext &renderContext, QgsPaintEffect *effect )
315 : mRenderContext( renderContext )
316 , mEffect( effect )
317 {
318 mPainter = mRenderContext.painter();
319 mPainter->save();
320 mEffect->begin( mRenderContext );
321 }
322
setEffect(QgsPaintEffect * effect)323 void QgsEffectPainter::setEffect( QgsPaintEffect *effect )
324 {
325 Q_ASSERT( !mEffect );
326
327 mEffect = effect;
328 mEffect->begin( mRenderContext );
329 }
330
~QgsEffectPainter()331 QgsEffectPainter::~QgsEffectPainter()
332 {
333 Q_ASSERT( mEffect );
334
335 mEffect->end( mRenderContext );
336 mPainter->restore();
337 }
338