QGIS API Documentation 3.39.0-Master (3aed037ce22)
Loading...
Searching...
No Matches
qgslayoutitempicture.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitempicture.cpp
3 ------------------------
4 begin : October 2017
5 copyright : (C) 2017 by 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
20#include "qgslayout.h"
23#include "qgslayoutitemmap.h"
24#include "qgslayoututils.h"
25#include "qgsmessagelog.h"
26#include "qgspathresolver.h"
27#include "qgsproperty.h"
29#include "qgssymbollayerutils.h"
30#include "qgscolorutils.h"
31#include "qgssvgcache.h"
32#include "qgslogger.h"
33#include "qgsreadwritecontext.h"
34#include "qgsimagecache.h"
36
37#include <QDomDocument>
38#include <QDomElement>
39#include <QFileInfo>
40#include <QImageReader>
41#include <QPainter>
42#include <QSvgRenderer>
43#include <QNetworkRequest>
44#include <QNetworkReply>
45#include <QEventLoop>
46#include <QCoreApplication>
47#include <QUrl>
48
50 : QgsLayoutItem( layout )
51 , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
52{
53 //default to no background
54 setBackgroundEnabled( false );
55
56 //connect some signals
57
58 //connect to atlas feature changing
59 //to update the picture source expression
60 connect( &layout->reportContext(), &QgsLayoutReportContext::changed, this, [this] { refreshPicture(); } );
61
62 //connect to layout print resolution changing
64
65 connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
66 connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemPicture::updateNorthArrowRotation );
67}
68
73
75{
76 return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPicture.svg" ) );
77}
78
83
85{
86 QPainter *painter = context.renderContext().painter();
87 const QgsScopedQPainterState painterState( painter );
88 // painter is scaled to dots, so scale back to layout units
89 painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
90
91 const bool prevSmoothTransform = painter->testRenderHint( QPainter::RenderHint::SmoothPixmapTransform );
92 if ( mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagAntialiasing ) )
93 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, true );
94
95 //picture resizing
96 if ( mMode != Qgis::PictureFormat::Unknown )
97 {
98 double boundRectWidthMM;
99 double boundRectHeightMM;
100 QRect imageRect;
101 if ( mResizeMode == QgsLayoutItemPicture::Zoom || mResizeMode == QgsLayoutItemPicture::ZoomResizeFrame )
102 {
103 boundRectWidthMM = mPictureWidth;
104 boundRectHeightMM = mPictureHeight;
105 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
106 }
107 else if ( mResizeMode == QgsLayoutItemPicture::Stretch )
108 {
109 boundRectWidthMM = rect().width();
110 boundRectHeightMM = rect().height();
111 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
112 }
113 else if ( mResizeMode == QgsLayoutItemPicture::Clip )
114 {
115 boundRectWidthMM = rect().width();
116 boundRectHeightMM = rect().height();
117 const int imageRectWidthPixels = mImage.width();
118 const int imageRectHeightPixels = mImage.height();
119 imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
120 QSize( imageRectWidthPixels, imageRectHeightPixels ) );
121 }
122 else
123 {
124 boundRectWidthMM = rect().width();
125 boundRectHeightMM = rect().height();
126 imageRect = QRect( 0, 0, mLayout->convertFromLayoutUnits( rect().width(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4,
127 mLayout->convertFromLayoutUnits( rect().height(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4 );
128 }
129
130 //zoom mode - calculate anchor point and rotation
131 if ( mResizeMode == Zoom )
132 {
133 //TODO - allow placement modes with rotation set. for now, setting a rotation
134 //always places picture in center of frame
135 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
136 {
137 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
138 painter->rotate( mPictureRotation );
139 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
140 }
141 else
142 {
143 //shift painter to edge/middle of frame depending on placement
144 const double diffX = rect().width() - boundRectWidthMM;
145 const double diffY = rect().height() - boundRectHeightMM;
146
147 double dX = 0;
148 double dY = 0;
149 switch ( mPictureAnchor )
150 {
151 case UpperLeft:
152 case MiddleLeft:
153 case LowerLeft:
154 //nothing to do
155 break;
156 case UpperMiddle:
157 case Middle:
158 case LowerMiddle:
159 dX = diffX / 2.0;
160 break;
161 case UpperRight:
162 case MiddleRight:
163 case LowerRight:
164 dX = diffX;
165 break;
166 }
167 switch ( mPictureAnchor )
168 {
169 case UpperLeft:
170 case UpperMiddle:
171 case UpperRight:
172 //nothing to do
173 break;
174 case MiddleLeft:
175 case Middle:
176 case MiddleRight:
177 dY = diffY / 2.0;
178 break;
179 case LowerLeft:
180 case LowerMiddle:
181 case LowerRight:
182 dY = diffY;
183 break;
184 }
185 painter->translate( dX, dY );
186 }
187 }
188 else if ( mResizeMode == ZoomResizeFrame )
189 {
190 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
191 {
192 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
193 painter->rotate( mPictureRotation );
194 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
195 }
196 }
197
198 if ( mMode == Qgis::PictureFormat::SVG )
199 {
200 mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
201 }
202 else if ( mMode == Qgis::PictureFormat::Raster )
203 {
204 painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
205 }
206 }
207 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, prevSmoothTransform );
208}
209
210QSizeF QgsLayoutItemPicture::applyItemSizeConstraint( const QSizeF targetSize )
211{
212 const QSizeF currentPictureSize = pictureSize();
213 QSizeF newSize = targetSize;
214 if ( mResizeMode == QgsLayoutItemPicture::Clip )
215 {
216 mPictureWidth = targetSize.width();
217 mPictureHeight = targetSize.height();
218 }
219 else
220 {
221 if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
222 {
223 QSizeF targetImageSize;
224 if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
225 {
226 targetImageSize = currentPictureSize;
227 }
228 else
229 {
230 //calculate aspect ratio of bounds of rotated image
231 QTransform tr;
232 tr.rotate( mPictureRotation );
233 const QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
234 targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
235 }
236
237 //if height has changed more than width, then fix width and set height correspondingly
238 //else, do the opposite
239 if ( std::fabs( rect().width() - targetSize.width() ) <
240 std::fabs( rect().height() - targetSize.height() ) )
241 {
242 newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
243 }
244 else
245 {
246 newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
247 }
248 }
249 else if ( mResizeMode == FrameToImageSize )
250 {
251 if ( !( currentPictureSize.isEmpty() ) )
252 {
253 const QgsLayoutSize sizeMM = mLayout->convertFromLayoutUnits( currentPictureSize, Qgis::LayoutUnit::Millimeters );
254 newSize.setWidth( sizeMM.width() * 25.4 / mLayout->renderContext().dpi() );
255 newSize.setHeight( sizeMM.height() * 25.4 / mLayout->renderContext().dpi() );
256 }
257 }
258
259 //find largest scaling of picture with this rotation which fits in item
260 if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
261 {
262 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ),
263 QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
264 mPictureWidth = rotatedImageRect.width();
265 mPictureHeight = rotatedImageRect.height();
266 }
267 else
268 {
269 mPictureWidth = newSize.width();
270 mPictureHeight = newSize.height();
271 }
272
273 if ( newSize != targetSize )
274 {
275 emit changed();
276 }
277 }
278
279 return newSize;
280}
281
282QRect QgsLayoutItemPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
283{
284 const int boundRectWidthPixels = boundRectWidthMM * mLayout->renderContext().dpi() / 25.4;
285 const int boundRectHeightPixels = boundRectHeightMM * mLayout->renderContext().dpi() / 25.4;
286
287 //update boundRectWidth/Height so that they exactly match pixel bounds
288 boundRectWidthMM = boundRectWidthPixels * 25.4 / mLayout->renderContext().dpi();
289 boundRectHeightMM = boundRectHeightPixels * 25.4 / mLayout->renderContext().dpi();
290
291 //calculate part of image which fits in bounds
292 int leftClip = 0;
293 int topClip = 0;
294
295 //calculate left crop
296 switch ( mPictureAnchor )
297 {
298 case UpperLeft:
299 case MiddleLeft:
300 case LowerLeft:
301 leftClip = 0;
302 break;
303 case UpperMiddle:
304 case Middle:
305 case LowerMiddle:
306 leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
307 break;
308 case UpperRight:
309 case MiddleRight:
310 case LowerRight:
311 leftClip = imageRectPixels.width() - boundRectWidthPixels;
312 break;
313 }
314
315 //calculate top crop
316 switch ( mPictureAnchor )
317 {
318 case UpperLeft:
319 case UpperMiddle:
320 case UpperRight:
321 topClip = 0;
322 break;
323 case MiddleLeft:
324 case Middle:
325 case MiddleRight:
326 topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
327 break;
328 case LowerLeft:
329 case LowerMiddle:
330 case LowerRight:
331 topClip = imageRectPixels.height() - boundRectHeightPixels;
332 break;
333 }
334
335 return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
336}
337
339{
340 const QgsExpressionContext scopedContext = createExpressionContext();
341 const QgsExpressionContext *evalContext = context ? context : &scopedContext;
342
343 mDataDefinedProperties.prepare( *evalContext );
344
345 QVariant source( mSourcePath );
346
347 //data defined source set?
348 mHasExpressionError = false;
350 {
352 bool ok = false;
354 source = sourceProperty.value( *evalContext, source, &ok );
355 if ( !ok || !source.canConvert( QMetaType::QString ) )
356 {
357 mHasExpressionError = true;
358 source = QString();
359 if ( scopedContext.feature().isValid() )
360 {
361 QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( tr( "Picture expression eval error" ), sourceProperty.asExpression() ) );
362 }
363 }
364 else if ( source.userType() != QMetaType::Type::QByteArray )
365 {
366 source = source.toString().trimmed();
367 QgsDebugMsgLevel( QStringLiteral( "exprVal PictureSource:%1" ).arg( source.toString() ), 2 );
368 }
369 }
370
371 loadPicture( source );
372}
373
374void QgsLayoutItemPicture::loadRemotePicture( const QString &url )
375{
376 //remote location
377
379 QEventLoop loop;
380 connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
381 fetcher.fetchContent( QUrl( url ) );
382
383 //wait until picture fetched
384 loop.exec( QEventLoop::ExcludeUserInputEvents );
385
386 QNetworkReply *reply = fetcher.reply();
387 if ( reply )
388 {
389 QImageReader imageReader( reply );
390 imageReader.setAutoTransform( true );
391
392 if ( imageReader.format() == "pdf" )
393 {
394 // special handling for this format -- we need to pass the desired target size onto the image reader
395 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
396 // a very low resolution image (the driver assumes points == pixels!)
397 // For other image formats, we read the original image size only and defer resampling to later in this
398 // function. That gives us more control over the resampling method used.
399
400 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
401 const QSize sizeAt72Dpi = imageReader.size();
402 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
403 imageReader.setScaledSize( sizeAtTargetDpi );
404 }
405
406 mImage = imageReader.read();
408 }
409 else
410 {
412 }
413}
414
415void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
416{
417 QFile pic;
418 pic.setFileName( path );
419
420 if ( !pic.exists() )
421 {
423 }
424 else
425 {
426 const QFileInfo sourceFileInfo( pic );
427 const QString sourceFileSuffix = sourceFileInfo.suffix();
428 if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
429 {
430 //try to open svg
433 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
434 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
435 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
436
437 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
438 1.0, 0, false, evaluatedParameters );
439 mSVG.load( svgContent );
440 if ( mSVG.isValid() )
441 {
443 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
444 mDefaultSvgSize.setWidth( viewBox.width() );
445 mDefaultSvgSize.setHeight( viewBox.height() );
446 }
447 else
448 {
450 }
451 }
452 else
453 {
454 //try to open raster with QImageReader
455 QImageReader imageReader( pic.fileName() );
456 imageReader.setAutoTransform( true );
457
458 if ( imageReader.format() == "pdf" )
459 {
460 // special handling for this format -- we need to pass the desired target size onto the image reader
461 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
462 // a very low resolution image (the driver assumes points == pixels!)
463 // For other image formats, we read the original image size only and defer resampling to later in this
464 // function. That gives us more control over the resampling method used.
465
466 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
467 const QSize sizeAt72Dpi = imageReader.size();
468 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
469 imageReader.setScaledSize( sizeAtTargetDpi );
470 }
471
472 if ( imageReader.read( &mImage ) )
473 {
475 }
476 else
477 {
479 }
480 }
481 }
482}
483
484void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
485{
486 if ( path.isEmpty() )
487 {
488 mImage = QImage();
489 mSVG.load( QByteArray() );
490 return;
491 }
492
493 switch ( mMode )
494 {
496 break;
497
499 {
500 bool fitsInCache = false;
501 bool isMissing = false;
502 mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true, mLayout->renderContext().dpi(), -1, &isMissing );
503 if ( mImage.isNull() || isMissing )
505 break;
506 }
507
509 {
512 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
513 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
514
515 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
516
517 bool isMissingImage = false;
518 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
519 1.0, 0, false, evaluatedParameters, &isMissingImage );
520 mSVG.load( svgContent );
521 if ( mSVG.isValid() && !isMissingImage )
522 {
524 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
525 mDefaultSvgSize.setWidth( viewBox.width() );
526 mDefaultSvgSize.setHeight( viewBox.height() );
527 }
528 else
529 {
531 }
532 break;
533 }
534 }
535}
536
537void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
538{
539 setPictureRotation( rotation );
540 emit pictureRotationChanged( rotation );
541}
542
543void QgsLayoutItemPicture::loadPicture( const QVariant &data )
544{
545 mIsMissingImage = false;
546 QVariant imageData( data );
547 mEvaluatedPath = data.toString();
548
549 if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == Qgis::PictureFormat::Unknown )
550 {
551 const QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
552 imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
553 }
554
555 if ( imageData.userType() == QMetaType::Type::QByteArray )
556 {
557 if ( mImage.loadFromData( imageData.toByteArray() ) )
558 {
560 }
561 }
562 else if ( mMode == Qgis::PictureFormat::Unknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
563 {
564 //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
565 loadRemotePicture( mEvaluatedPath );
566 }
567 else if ( mMode == Qgis::PictureFormat::Unknown )
568 {
569 //local location - for old API/project compatibility only!!
570 loadLocalPicture( mEvaluatedPath );
571 }
572 else
573 {
574 loadPictureUsingCache( mEvaluatedPath );
575 }
576
577 if ( mMode != Qgis::PictureFormat::Unknown ) //make sure we start with a new QImage
578 {
580 }
581 else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
582 {
583 //trying to load an invalid file or bad expression, show cross picture
584 mIsMissingImage = true;
585 if ( mOriginalMode == Qgis::PictureFormat::Raster )
586 {
587 const QString badFile( QStringLiteral( ":/images/composer/missing_image.png" ) );
588 QImageReader imageReader( badFile );
589 if ( imageReader.read( &mImage ) )
591 }
592 else if ( mOriginalMode == Qgis::PictureFormat::SVG )
593 {
594 const QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
595 mSVG.load( badFile );
596 if ( mSVG.isValid() )
597 {
599 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
600 mDefaultSvgSize.setWidth( viewBox.width() );
601 mDefaultSvgSize.setHeight( viewBox.height() );
602 }
603 }
605 }
606
607 update();
608 emit changed();
609}
610
611QSizeF QgsLayoutItemPicture::pictureSize()
612{
613 if ( mMode == Qgis::PictureFormat::SVG )
614 {
615 return mDefaultSvgSize;
616 }
617 else if ( mMode == Qgis::PictureFormat::Raster )
618 {
619 return QSizeF( mImage.width(), mImage.height() );
620 }
621 else
622 {
623 return QSizeF( 0, 0 );
624 }
625}
626
628{
629 return mIsMissingImage;
630}
631
633{
634 return mEvaluatedPath;
635}
636
637QMap<QString, QgsProperty> QgsLayoutItemPicture::svgDynamicParameters() const
638{
639 const QVariantMap parameters = mCustomProperties.value( QStringLiteral( "svg-dynamic-parameters" ), QVariantMap() ).toMap();
640 return QgsProperty::variantMapToPropertyMap( parameters );
641}
642
643void QgsLayoutItemPicture::setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters )
644{
645 const QVariantMap variantMap = QgsProperty::propertyMapToVariantMap( parameters );
646 mCustomProperties.setValue( QStringLiteral( "svg-dynamic-parameters" ), variantMap );
648}
649
650void QgsLayoutItemPicture::shapeChanged()
651{
652 if ( mMode == Qgis::PictureFormat::SVG && !mLoadingSvg )
653 {
654 mLoadingSvg = true;
656 mLoadingSvg = false;
657 }
658}
659
661{
662 const double oldRotation = mPictureRotation;
663 mPictureRotation = rotation;
664
665 if ( mResizeMode == Zoom )
666 {
667 //find largest scaling of picture with this rotation which fits in item
668 const QSizeF currentPictureSize = pictureSize();
669 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
670 mPictureWidth = rotatedImageRect.width();
671 mPictureHeight = rotatedImageRect.height();
672 update();
673 }
674 else if ( mResizeMode == ZoomResizeFrame )
675 {
676 const QSizeF currentPictureSize = pictureSize();
677 const QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
678
679 //calculate actual size of image inside frame
680 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
681
682 //rotate image rect by new rotation and get bounding box
683 QTransform tr;
684 tr.rotate( mPictureRotation );
685 QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
686
687 //keep the center in the same location
688 newRect.moveCenter( oldRect.center() );
689 attemptSetSceneRect( newRect );
690 emit changed();
691 }
692
693 emit pictureRotationChanged( mPictureRotation );
694}
695
697{
698 mNorthArrowHandler->setLinkedMap( map );
699}
700
702{
703 mResizeMode = mode;
705 || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
706 {
707 //call set scene rect to force item to resize to fit picture
709 }
710 update();
711}
712
714{
715 //call set scene rect with current position/size, as this will trigger the
716 //picture item to recalculate its frame and image size
717 attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
718}
719
732
734{
735 mOriginalMode = format;
736 mMode = format;
737 mSourcePath = path;
739}
740
742{
743 return mSourcePath;
744}
745
746bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
747{
748 QString imagePath = mSourcePath;
749
750 // convert from absolute path to relative. For SVG we also need to consider system SVG paths
751 const QgsPathResolver pathResolver = context.pathResolver();
752 if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
753 imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
754 else
755 imagePath = pathResolver.writePath( imagePath );
756
757 elem.setAttribute( QStringLiteral( "file" ), imagePath );
758 elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
759 elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
760 elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
761 elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
762 elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsColorUtils::colorToString( mSvgFillColor ) );
763 elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsColorUtils::colorToString( mSvgStrokeColor ) );
764 elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
765 elem.setAttribute( QStringLiteral( "mode" ), static_cast< int >( mOriginalMode ) );
766
767 //rotation
768 elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
769 if ( !mNorthArrowHandler->linkedMap() )
770 {
771 elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
772 }
773 else
774 {
775 elem.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
776 }
777 elem.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
778 elem.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
779 return true;
780}
781
782bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
783{
784 mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
785 mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
786 mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
787 //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
788 mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
789
790 mSvgFillColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsColorUtils::colorToString( QColor( 255, 255, 255 ) ) ) );
791 mSvgStrokeColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsColorUtils::colorToString( QColor( 0, 0, 0 ) ) ) );
792 mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
793 mOriginalMode = static_cast< Qgis::PictureFormat >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( static_cast< int >( Qgis::PictureFormat::Unknown ) ) ).toInt() );
794 mMode = mOriginalMode;
795
796 const QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
797 if ( !composerItemList.isEmpty() )
798 {
799 const QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
800
801 if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
802 {
803 //in versions prior to 2.1 picture rotation was stored in the rotation attribute
804 mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
805 }
806 }
807
808 mDefaultSvgSize = QSize( 0, 0 );
809
810 if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
811 {
812 //update pre 2.5 picture expression to use data defined expression
813 const QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QString() );
814 const QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
815 bool expressionActive;
816 expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
817
819 }
820
821 QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
822
823 // convert from relative path to absolute. For SVG we also need to consider system SVG paths
824 const QgsPathResolver pathResolver = context.pathResolver();
825 if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
826 imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
827 else
828 imagePath = pathResolver.readPath( imagePath );
829
830 mSourcePath = imagePath;
831
832 //picture rotation
833 if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
834 {
835 mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
836 }
837
838 //rotation map
839 mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
840 mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
841
842 mNorthArrowHandler->setLinkedMap( nullptr );
843 mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
844
845 return true;
846}
847
849{
850 return mNorthArrowHandler->linkedMap();
851}
852
854{
855 return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
856}
857
862
864{
865 return mNorthArrowHandler->northOffset();
866}
867
869{
870 mNorthArrowHandler->setNorthOffset( offset );
871}
872
874{
875 mPictureAnchor = anchor;
876 update();
877}
878
879void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
880{
881 mSvgFillColor = color;
883}
884
886{
887 mSvgStrokeColor = color;
889}
890
892{
893 mSvgStrokeWidth = width;
895}
896
898{
899 if ( mOriginalMode == mode )
900 return;
901
902 mOriginalMode = mode;
903 mMode = mode;
905}
906
908{
909 if ( !mLayout || mRotationMapUuid.isEmpty() )
910 {
911 mNorthArrowHandler->setLinkedMap( nullptr );
912 }
913 else
914 {
915 mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
916 }
917
919}
@ Millimeters
Millimeters.
PictureFormat
Picture formats.
Definition qgis.h:4648
@ Raster
Raster image.
@ Unknown
Invalid or unknown image type.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
bool isValid() const
Returns the validity of this feature.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
Layout graphical items for displaying a map.
A layout item subclass that displays SVG files or raster format images (jpg, png, ....
void setResizeMode(QgsLayoutItemPicture::ResizeMode mode)
Sets the resize mode used for drawing the picture within the item bounds.
void setMode(Qgis::PictureFormat mode)
Sets the current picture mode (image format).
void setSvgDynamicParameters(const QMap< QString, QgsProperty > &parameters)
Sets the SVG dynamic parameters.
QMap< QString, QgsProperty > svgDynamicParameters() const
Returns the SVG dynamic parameters.
QString picturePath() const
Returns the path of the source image.
void setPictureAnchor(QgsLayoutItem::ReferencePoint anchor)
Sets the picture's anchor point, which controls how it is placed within the picture item's frame.
void setNorthMode(NorthMode mode)
Sets the mode used to align the picture to a map's North.
double northOffset() const
Returns the offset added to the picture's rotation from a map's North.
QgsLayoutItemPicture(QgsLayout *layout)
Constructor for QgsLayoutItemPicture, with the specified parent layout.
void setLinkedMap(QgsLayoutItemMap *map)
Sets the map object for rotation.
void setPicturePath(const QString &path, Qgis::PictureFormat format=Qgis::PictureFormat::Unknown)
Sets the source path of the image (may be svg or a raster format).
void setPictureRotation(double rotation)
Sets the picture rotation within the item bounds, in degrees clockwise.
bool isMissingImage() const
Returns true if the source image is missing and the picture cannot be rendered.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
void setSvgStrokeWidth(double width)
Sets the stroke width (in layout units) used for parametrized SVG files.
QgsLayoutItemMap * linkedMap() const
Returns the linked rotation map, if set.
void pictureRotationChanged(double newRotation)
Emitted on picture rotation change.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
static QgsLayoutItemPicture * create(QgsLayout *layout)
Returns a new picture item for the specified layout.
Qgis::PictureFormat mode() const
Returns the current picture mode (image format), FormatUnknown if given picture format is unknown.
NorthMode
Method for syncing rotation to a map's North direction.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
QString evaluatedPath() const
Returns the current evaluated picture path, which includes the result of data defined path overrides.
QSizeF applyItemSizeConstraint(QSizeF targetSize) override
Applies any item-specific size constraint handling to a given targetSize in layout units.
void setSvgStrokeColor(const QColor &color)
Sets the stroke color used for parametrized SVG files.
int type() const override
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties) override
QIcon icon() const override
Returns the item's icon.
void refreshPicture(const QgsExpressionContext *context=nullptr)
Recalculates the source image (if using an expression for picture's source) and reloads and redraws t...
NorthMode northMode() const
Returns the mode used to align the picture to a map's North.
void setSvgFillColor(const QColor &color)
Sets the fill color used for parametrized SVG files.
void recalculateSize()
Forces a recalculation of the picture's frame size.
ResizeMode
Controls how pictures are scaled within the item's frame.
@ FrameToImageSize
Sets size of frame to match original size of image without scaling.
@ ZoomResizeFrame
Enlarges image to fit frame, then resizes frame to fit resultant image.
@ Clip
Draws image at original size and clips any portion which falls outside frame.
@ Stretch
Stretches image to fit frame, ignores aspect ratio.
@ Zoom
Enlarges image to fit frame while maintaining aspect ratio of picture.
void setNorthOffset(double offset)
Sets the offset added to the picture's rotation from a map's North.
Contains settings and helpers relating to a render of a QgsLayoutItem.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Base class for graphical items within a QgsLayout.
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties)
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
ReferencePoint
Fixed position reference point.
@ LowerMiddle
Lower center of item.
@ MiddleLeft
Middle left of item.
@ Middle
Center of item.
@ UpperRight
Upper right corner of item.
@ LowerLeft
Lower left corner of item.
@ UpperLeft
Upper left corner of item.
@ UpperMiddle
Upper center of item.
@ MiddleRight
Middle right of item.
@ LowerRight
Lower right corner of item.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void sizePositionChanged()
Emitted when the item's size or position changes.
virtual QString uuid() const
Returns the item identification string.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
An object which handles north-arrow type behavior for layout items.
void setNorthOffset(double offset)
Sets the offset added to the arrows's rotation from a map's North.
NorthMode northMode() const
Returns the mode used to calculate the arrow rotation.
QgsLayoutItemMap * linkedMap() const
Returns the linked rotation map, if set.
void arrowRotationChanged(double newRotation)
Emitted on arrow rotation change.
NorthMode
Method for syncing rotation to a map's North direction.
void setNorthMode(NorthMode mode)
Sets the mode used to calculate the arrow rotation.
void setLinkedMap(QgsLayoutItemMap *map)
Sets the linked map item.
double northOffset() const
Returns the offset added to the arrows's rotation from a map's North.
QgsObjectCustomProperties mCustomProperties
Custom properties for object.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
DataDefinedProperty
Data defined properties for different item types.
@ PictureSvgStrokeColor
SVG stroke color.
@ PictureSvgBackgroundColor
SVG background color.
@ PictureSource
Picture source url.
@ PictureSvgStrokeWidth
SVG stroke width.
@ AllProperties
All properties for item.
void dpiChanged()
Emitted when the context's DPI is changed.
@ FlagAntialiasing
Use antialiasing when drawing items.
void changed()
Emitted certain settings in the context is changed, e.g.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
double height() const
Returns the height of the size.
void setWidth(const double width)
Sets the width for the size.
double width() const
Returns the width of the size.
static QRectF largestRotatedRectWithinBounds(const QRectF &originalRect, const QRectF &boundsRect, double rotation)
Calculates the largest scaled version of originalRect which fits within boundsRect,...
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:49
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
QgsLayoutReportContext & reportContext()
Returns a reference to the layout's report context, which stores information relating to the current ...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
HTTP network content fetcher.
void finished()
Emitted when content has loaded.
QNetworkReply * reply()
Returns a reference to the network reply.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
void setValue(const QString &key, const QVariant &value)
Add an entry to the store with the specified key.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Returns the value for the given key.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const final
Prepares the collection against a specified expression context.
QgsProperty property(int key) const final
Returns a matching property from the collection, if one exists.
A store for object properties.
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
static QVariantMap propertyMapToVariantMap(const QMap< QString, QgsProperty > &propertyMap)
Convert a map of QgsProperty to a map of QVariant This is useful to save a map of properties.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QMap< QString, QgsProperty > variantMapToPropertyMap(const QVariantMap &variantMap)
Convert a map of QVariant to a map of QgsProperty This is useful to restore a map of properties.
The class is used as a container of context for various read/write operations on other objects.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >(), bool *isMissingImage=nullptr)
Gets the SVG content corresponding to the given path.
static QgsStringMap evaluatePropertiesMap(const QMap< QString, QgsProperty > &propertiesMap, const QgsExpressionContext &context)
Evaluates a map of properties using the given context and returns a variant map with evaluated expres...
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5652
QMap< QString, QString > QgsStringMap
Definition qgis.h:6190
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39