Branch data Line data Source code
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 "qgssymbollayerutils.h" 23 : : #include <QPicture> 24 : : 25 : : Q_GUI_EXPORT extern int qt_defaultDpiX(); 26 : : Q_GUI_EXPORT extern int qt_defaultDpiY(); 27 : : 28 : 0 : QgsPaintEffect::QgsPaintEffect( const QgsPaintEffect &other ) 29 : 0 : : mEnabled( other.enabled() ) 30 : 0 : , mDrawMode( other.drawMode() ) 31 : 0 : { 32 : : 33 : 0 : } 34 : : 35 : 519 : QgsPaintEffect::~QgsPaintEffect() 36 : 519 : { 37 : 519 : if ( mOwnsImage ) 38 : : { 39 : 0 : delete mSourceImage; 40 : 0 : } 41 : 519 : delete mEffectPainter; 42 : 519 : delete mTempPicture; 43 : 519 : } 44 : : 45 : 390 : void QgsPaintEffect::setEnabled( const bool enabled ) 46 : : { 47 : 390 : mEnabled = enabled; 48 : 390 : } 49 : : 50 : 0 : void QgsPaintEffect::setDrawMode( const QgsPaintEffect::DrawMode drawMode ) 51 : : { 52 : 0 : mDrawMode = drawMode; 53 : 0 : } 54 : : 55 : 0 : bool QgsPaintEffect::saveProperties( QDomDocument &doc, QDomElement &element ) const 56 : : { 57 : 0 : if ( element.isNull() ) 58 : : { 59 : 0 : return false; 60 : : } 61 : 0 : QDomElement effectElement = doc.createElement( QStringLiteral( "effect" ) ); 62 : 0 : effectElement.setAttribute( QStringLiteral( "type" ), type() ); 63 : 0 : QgsSymbolLayerUtils::saveProperties( properties(), doc, effectElement ); 64 : 0 : element.appendChild( effectElement ); 65 : 0 : return true; 66 : 0 : } 67 : : 68 : 195 : bool QgsPaintEffect::readProperties( const QDomElement &element ) 69 : : { 70 : 195 : if ( element.isNull() ) 71 : : { 72 : 0 : return false; 73 : : } 74 : : 75 : 195 : QVariantMap props = QgsSymbolLayerUtils::parseProperties( element ); 76 : 195 : readProperties( props ); 77 : 195 : return true; 78 : 195 : } 79 : : 80 : 0 : void QgsPaintEffect::render( QPicture &picture, QgsRenderContext &context ) 81 : : { 82 : : //set source picture 83 : 0 : mPicture = &picture; 84 : 0 : delete mSourceImage; 85 : 0 : mSourceImage = nullptr; 86 : : 87 : 0 : draw( context ); 88 : 0 : } 89 : : 90 : 0 : void QgsPaintEffect::begin( QgsRenderContext &context ) 91 : : { 92 : : //temporarily replace painter and direct paint operations for context to a QPicture 93 : 0 : mPrevPainter = context.painter(); 94 : : 95 : 0 : delete mTempPicture; 96 : 0 : mTempPicture = new QPicture(); 97 : : 98 : 0 : delete mEffectPainter; 99 : 0 : mEffectPainter = new QPainter(); 100 : 0 : mEffectPainter->begin( mTempPicture ); 101 : : 102 : 0 : context.setPainter( mEffectPainter ); 103 : 0 : } 104 : : 105 : 0 : void QgsPaintEffect::end( QgsRenderContext &context ) 106 : : { 107 : 0 : if ( !mEffectPainter ) 108 : 0 : return; 109 : : 110 : 0 : mEffectPainter->end(); 111 : 0 : delete mEffectPainter; 112 : 0 : mEffectPainter = nullptr; 113 : : 114 : : //restore previous painter for context 115 : 0 : context.setPainter( mPrevPainter ); 116 : 0 : mPrevPainter = nullptr; 117 : : 118 : : // clear any existing pen/brush - sometimes these are not correctly restored when restoring a painter 119 : : // with a QPicture destination - see #15696 120 : 0 : context.painter()->setPen( Qt::NoPen ); 121 : 0 : context.painter()->setBrush( Qt::NoBrush ); 122 : : 123 : : //draw using effect 124 : 0 : render( *mTempPicture, context ); 125 : : 126 : : //clean up 127 : 0 : delete mTempPicture; 128 : 0 : mTempPicture = nullptr; 129 : 0 : } 130 : : 131 : 0 : void QgsPaintEffect::drawSource( QPainter &painter ) 132 : : { 133 : 0 : if ( requiresQPainterDpiFix ) 134 : : { 135 : 0 : QgsScopedQPainterState painterState( &painter ); 136 : 0 : fixQPictureDpi( &painter ); 137 : 0 : painter.drawPicture( 0, 0, *mPicture ); 138 : 0 : } 139 : : else 140 : : { 141 : 0 : painter.drawPicture( 0, 0, *mPicture ); 142 : : } 143 : 0 : } 144 : : 145 : 0 : QImage *QgsPaintEffect::sourceAsImage( QgsRenderContext &context ) 146 : : { 147 : : //have we already created a source image? if so, return it 148 : 0 : if ( mSourceImage ) 149 : : { 150 : 0 : return mSourceImage; 151 : : } 152 : : 153 : 0 : if ( !mPicture ) 154 : 0 : return nullptr; 155 : : 156 : : //else create it 157 : : //TODO - test with premultiplied image for speed 158 : 0 : QRectF bounds = imageBoundingRect( context ); 159 : 0 : mSourceImage = new QImage( bounds.width(), bounds.height(), QImage::Format_ARGB32 ); 160 : 0 : mSourceImage->fill( Qt::transparent ); 161 : 0 : QPainter imagePainter( mSourceImage ); 162 : 0 : imagePainter.setRenderHint( QPainter::Antialiasing ); 163 : 0 : imagePainter.translate( -bounds.left(), -bounds.top() ); 164 : 0 : imagePainter.drawPicture( 0, 0, *mPicture ); 165 : 0 : imagePainter.end(); 166 : 0 : mOwnsImage = true; 167 : 0 : return mSourceImage; 168 : 0 : } 169 : : 170 : 0 : QPointF QgsPaintEffect::imageOffset( const QgsRenderContext &context ) const 171 : : { 172 : 0 : return imageBoundingRect( context ).topLeft(); 173 : : } 174 : : 175 : 0 : QRectF QgsPaintEffect::boundingRect( const QRectF &rect, const QgsRenderContext &context ) const 176 : : { 177 : 0 : Q_UNUSED( context ) 178 : 0 : return rect; 179 : : } 180 : : 181 : 0 : void QgsPaintEffect::fixQPictureDpi( QPainter *painter ) const 182 : : { 183 : : // QPicture makes an assumption that we drawing to it with system DPI. 184 : : // Then when being drawn, it scales the painter. The following call 185 : : // negates the effect. There is no way of setting QPicture's DPI. 186 : : // See QTBUG-20361 187 : 0 : painter->scale( static_cast< double >( qt_defaultDpiX() ) / painter->device()->logicalDpiX(), 188 : 0 : static_cast< double >( qt_defaultDpiY() ) / painter->device()->logicalDpiY() ); 189 : 0 : } 190 : : 191 : 0 : QRectF QgsPaintEffect::imageBoundingRect( const QgsRenderContext &context ) const 192 : : { 193 : 0 : return boundingRect( mPicture->boundingRect(), context ); 194 : : } 195 : : 196 : : 197 : : // 198 : : // QgsDrawSourceEffect 199 : : // 200 : : 201 : 40 : QgsPaintEffect *QgsDrawSourceEffect::create( const QVariantMap &map ) 202 : : { 203 : 40 : QgsDrawSourceEffect *effect = new QgsDrawSourceEffect(); 204 : 40 : effect->readProperties( map ); 205 : 40 : return effect; 206 : : } 207 : : 208 : 0 : void QgsDrawSourceEffect::draw( QgsRenderContext &context ) 209 : : { 210 : 0 : if ( !enabled() || !context.painter() ) 211 : 0 : return; 212 : : 213 : 0 : QPainter *painter = context.painter(); 214 : : 215 : 0 : if ( mBlendMode == QPainter::CompositionMode_SourceOver && qgsDoubleNear( mOpacity, 1.0 ) ) 216 : : { 217 : : //just draw unmodified source 218 : 0 : drawSource( *painter ); 219 : 0 : } 220 : : else 221 : : { 222 : : //rasterize source and apply modifications 223 : 0 : QImage image = sourceAsImage( context )->copy(); 224 : 0 : QgsImageOperation::multiplyOpacity( image, mOpacity ); 225 : 0 : QgsScopedQPainterState painterState( painter ); 226 : 0 : painter->setCompositionMode( mBlendMode ); 227 : 0 : painter->drawImage( imageOffset( context ), image ); 228 : 0 : } 229 : 0 : } 230 : : 231 : 0 : QgsDrawSourceEffect *QgsDrawSourceEffect::clone() const 232 : : { 233 : 0 : return new QgsDrawSourceEffect( *this ); 234 : 0 : } 235 : : 236 : 0 : QVariantMap QgsDrawSourceEffect::properties() const 237 : : { 238 : 0 : QVariantMap props; 239 : 0 : props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" ); 240 : 0 : props.insert( QStringLiteral( "draw_mode" ), QString::number( int( mDrawMode ) ) ); 241 : 0 : props.insert( QStringLiteral( "blend_mode" ), QString::number( int( mBlendMode ) ) ); 242 : 0 : props.insert( QStringLiteral( "opacity" ), QString::number( mOpacity ) ); 243 : 0 : return props; 244 : 0 : } 245 : : 246 : 80 : void QgsDrawSourceEffect::readProperties( const QVariantMap &props ) 247 : : { 248 : : bool ok; 249 : 160 : QPainter::CompositionMode mode = static_cast< QPainter::CompositionMode >( props.value( QStringLiteral( "blend_mode" ) ).toInt( &ok ) ); 250 : 80 : if ( ok ) 251 : : { 252 : 40 : mBlendMode = mode; 253 : 40 : } 254 : 160 : if ( props.contains( QStringLiteral( "transparency" ) ) ) 255 : : { 256 : 0 : double transparency = props.value( QStringLiteral( "transparency" ) ).toDouble( &ok ); 257 : 0 : if ( ok ) 258 : : { 259 : 0 : mOpacity = 1.0 - transparency; 260 : 0 : } 261 : 0 : } 262 : : else 263 : : { 264 : 160 : double opacity = props.value( QStringLiteral( "opacity" ) ).toDouble( &ok ); 265 : 80 : if ( ok ) 266 : : { 267 : 40 : mOpacity = opacity; 268 : 40 : } 269 : : } 270 : 240 : mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt(); 271 : 240 : mDrawMode = static_cast< QgsPaintEffect::DrawMode >( props.value( QStringLiteral( "draw_mode" ), QStringLiteral( "2" ) ).toInt() ); 272 : 80 : } 273 : : 274 : : 275 : : // 276 : : // QgsEffectPainter 277 : : // 278 : : 279 : 0 : QgsEffectPainter::QgsEffectPainter( QgsRenderContext &renderContext ) 280 : 0 : : mRenderContext( renderContext ) 281 : : 282 : : { 283 : 0 : mPainter = renderContext.painter(); 284 : 0 : mPainter->save(); 285 : 0 : } 286 : : 287 : 0 : QgsEffectPainter::QgsEffectPainter( QgsRenderContext &renderContext, QgsPaintEffect *effect ) 288 : 0 : : mRenderContext( renderContext ) 289 : 0 : , mEffect( effect ) 290 : : { 291 : 0 : mPainter = mRenderContext.painter(); 292 : 0 : mPainter->save(); 293 : 0 : mEffect->begin( mRenderContext ); 294 : 0 : } 295 : : 296 : 0 : void QgsEffectPainter::setEffect( QgsPaintEffect *effect ) 297 : : { 298 : : Q_ASSERT( !mEffect ); 299 : 0 : 300 : 0 : mEffect = effect; 301 : 0 : mEffect->begin( mRenderContext ); 302 : 0 : } 303 : 0 : 304 : 0 : QgsEffectPainter::~QgsEffectPainter() 305 : 0 : { 306 : : Q_ASSERT( mEffect ); 307 : : 308 : 0 : mEffect->end( mRenderContext ); 309 : 0 : mPainter->restore(); 310 : 0 : }