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