QGIS API Documentation 3.41.0-Master (3440c17df1d)
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
19#include "moc_qgslayoutitempicture.cpp"
21#include "qgslayout.h"
24#include "qgslayoutitemmap.h"
25#include "qgslayoututils.h"
26#include "qgsmessagelog.h"
27#include "qgspathresolver.h"
28#include "qgsproperty.h"
30#include "qgssymbollayerutils.h"
31#include "qgscolorutils.h"
32#include "qgssvgcache.h"
33#include "qgslogger.h"
34#include "qgsreadwritecontext.h"
35#include "qgsimagecache.h"
37
38#include <QDomDocument>
39#include <QDomElement>
40#include <QFileInfo>
41#include <QImageReader>
42#include <QPainter>
43#include <QSvgRenderer>
44#include <QNetworkRequest>
45#include <QNetworkReply>
46#include <QEventLoop>
47#include <QCoreApplication>
48#include <QUrl>
49
51 : QgsLayoutItem( layout )
52 , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
53{
54 //default to no background
55 setBackgroundEnabled( false );
56
57 //connect some signals
58
59 //connect to atlas feature changing
60 //to update the picture source expression
61 connect( &layout->reportContext(), &QgsLayoutReportContext::changed, this, [this] { refreshPicture(); } );
62
63 //connect to layout print resolution changing
65
66 connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
67 connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemPicture::updateNorthArrowRotation );
68}
69
74
76{
77 return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPicture.svg" ) );
78}
79
84
86{
87 QPainter *painter = context.renderContext().painter();
88 const QgsScopedQPainterState painterState( painter );
89 // painter is scaled to dots, so scale back to layout units
90 painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
91
92 const bool prevSmoothTransform = painter->testRenderHint( QPainter::RenderHint::SmoothPixmapTransform );
93 if ( mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagAntialiasing ) )
94 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, true );
95
96 //picture resizing
97 if ( mMode != Qgis::PictureFormat::Unknown )
98 {
99 double boundRectWidthMM;
100 double boundRectHeightMM;
101 QRect imageRect;
102 if ( mResizeMode == QgsLayoutItemPicture::Zoom || mResizeMode == QgsLayoutItemPicture::ZoomResizeFrame )
103 {
104 boundRectWidthMM = mPictureWidth;
105 boundRectHeightMM = mPictureHeight;
106 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
107 }
108 else if ( mResizeMode == QgsLayoutItemPicture::Stretch )
109 {
110 boundRectWidthMM = rect().width();
111 boundRectHeightMM = rect().height();
112 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
113 }
114 else if ( mResizeMode == QgsLayoutItemPicture::Clip )
115 {
116 boundRectWidthMM = rect().width();
117 boundRectHeightMM = rect().height();
118 const int imageRectWidthPixels = mImage.width();
119 const int imageRectHeightPixels = mImage.height();
120 imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
121 QSize( imageRectWidthPixels, imageRectHeightPixels ) );
122 }
123 else
124 {
125 boundRectWidthMM = rect().width();
126 boundRectHeightMM = rect().height();
127 imageRect = QRect( 0, 0, mLayout->convertFromLayoutUnits( rect().width(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4,
128 mLayout->convertFromLayoutUnits( rect().height(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4 );
129 }
130
131 //zoom mode - calculate anchor point and rotation
132 if ( mResizeMode == Zoom )
133 {
134 //TODO - allow placement modes with rotation set. for now, setting a rotation
135 //always places picture in center of frame
136 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
137 {
138 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
139 painter->rotate( mPictureRotation );
140 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
141 }
142 else
143 {
144 //shift painter to edge/middle of frame depending on placement
145 const double diffX = rect().width() - boundRectWidthMM;
146 const double diffY = rect().height() - boundRectHeightMM;
147
148 double dX = 0;
149 double dY = 0;
150 switch ( mPictureAnchor )
151 {
152 case UpperLeft:
153 case MiddleLeft:
154 case LowerLeft:
155 //nothing to do
156 break;
157 case UpperMiddle:
158 case Middle:
159 case LowerMiddle:
160 dX = diffX / 2.0;
161 break;
162 case UpperRight:
163 case MiddleRight:
164 case LowerRight:
165 dX = diffX;
166 break;
167 }
168 switch ( mPictureAnchor )
169 {
170 case UpperLeft:
171 case UpperMiddle:
172 case UpperRight:
173 //nothing to do
174 break;
175 case MiddleLeft:
176 case Middle:
177 case MiddleRight:
178 dY = diffY / 2.0;
179 break;
180 case LowerLeft:
181 case LowerMiddle:
182 case LowerRight:
183 dY = diffY;
184 break;
185 }
186 painter->translate( dX, dY );
187 }
188 }
189 else if ( mResizeMode == ZoomResizeFrame )
190 {
191 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
192 {
193 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
194 painter->rotate( mPictureRotation );
195 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
196 }
197 }
198
199 if ( mMode == Qgis::PictureFormat::SVG )
200 {
201 mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
202 }
203 else if ( mMode == Qgis::PictureFormat::Raster )
204 {
205 painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
206 }
207 }
208 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, prevSmoothTransform );
209}
210
211QSizeF QgsLayoutItemPicture::applyItemSizeConstraint( const QSizeF targetSize )
212{
213 const QSizeF currentPictureSize = pictureSize();
214 QSizeF newSize = targetSize;
215 if ( mResizeMode == QgsLayoutItemPicture::Clip )
216 {
217 mPictureWidth = targetSize.width();
218 mPictureHeight = targetSize.height();
219 }
220 else
221 {
222 if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
223 {
224 QSizeF targetImageSize;
225 if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
226 {
227 targetImageSize = currentPictureSize;
228 }
229 else
230 {
231 //calculate aspect ratio of bounds of rotated image
232 QTransform tr;
233 tr.rotate( mPictureRotation );
234 const QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
235 targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
236 }
237
238 //if height has changed more than width, then fix width and set height correspondingly
239 //else, do the opposite
240 if ( std::fabs( rect().width() - targetSize.width() ) <
241 std::fabs( rect().height() - targetSize.height() ) )
242 {
243 newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
244 }
245 else
246 {
247 newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
248 }
249 }
250 else if ( mResizeMode == FrameToImageSize )
251 {
252 if ( !( currentPictureSize.isEmpty() ) )
253 {
254 const QgsLayoutSize sizeMM = mLayout->convertFromLayoutUnits( currentPictureSize, Qgis::LayoutUnit::Millimeters );
255 newSize.setWidth( sizeMM.width() * 25.4 / mLayout->renderContext().dpi() );
256 newSize.setHeight( sizeMM.height() * 25.4 / mLayout->renderContext().dpi() );
257 }
258 }
259
260 //find largest scaling of picture with this rotation which fits in item
261 if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
262 {
263 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ),
264 QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
265 mPictureWidth = rotatedImageRect.width();
266 mPictureHeight = rotatedImageRect.height();
267 }
268 else
269 {
270 mPictureWidth = newSize.width();
271 mPictureHeight = newSize.height();
272 }
273
274 if ( newSize != targetSize )
275 {
276 emit changed();
277 }
278 }
279
280 return newSize;
281}
282
283QRect QgsLayoutItemPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
284{
285 const int boundRectWidthPixels = boundRectWidthMM * mLayout->renderContext().dpi() / 25.4;
286 const int boundRectHeightPixels = boundRectHeightMM * mLayout->renderContext().dpi() / 25.4;
287
288 //update boundRectWidth/Height so that they exactly match pixel bounds
289 boundRectWidthMM = boundRectWidthPixels * 25.4 / mLayout->renderContext().dpi();
290 boundRectHeightMM = boundRectHeightPixels * 25.4 / mLayout->renderContext().dpi();
291
292 //calculate part of image which fits in bounds
293 int leftClip = 0;
294 int topClip = 0;
295
296 //calculate left crop
297 switch ( mPictureAnchor )
298 {
299 case UpperLeft:
300 case MiddleLeft:
301 case LowerLeft:
302 leftClip = 0;
303 break;
304 case UpperMiddle:
305 case Middle:
306 case LowerMiddle:
307 leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
308 break;
309 case UpperRight:
310 case MiddleRight:
311 case LowerRight:
312 leftClip = imageRectPixels.width() - boundRectWidthPixels;
313 break;
314 }
315
316 //calculate top crop
317 switch ( mPictureAnchor )
318 {
319 case UpperLeft:
320 case UpperMiddle:
321 case UpperRight:
322 topClip = 0;
323 break;
324 case MiddleLeft:
325 case Middle:
326 case MiddleRight:
327 topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
328 break;
329 case LowerLeft:
330 case LowerMiddle:
331 case LowerRight:
332 topClip = imageRectPixels.height() - boundRectHeightPixels;
333 break;
334 }
335
336 return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
337}
338
340{
341 const QgsExpressionContext scopedContext = createExpressionContext();
342 const QgsExpressionContext *evalContext = context ? context : &scopedContext;
343
344 mDataDefinedProperties.prepare( *evalContext );
345
346 QVariant source( mSourcePath );
347
348 //data defined source set?
349 mHasExpressionError = false;
351 {
353 bool ok = false;
355 source = sourceProperty.value( *evalContext, source, &ok );
356 if ( !ok || !source.canConvert( QMetaType::QString ) )
357 {
358 mHasExpressionError = true;
359 source = QString();
360 if ( scopedContext.feature().isValid() )
361 {
362 QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( tr( "Picture expression eval error" ), sourceProperty.asExpression() ) );
363 }
364 }
365 else if ( source.userType() != QMetaType::Type::QByteArray )
366 {
367 source = source.toString().trimmed();
368 QgsDebugMsgLevel( QStringLiteral( "exprVal PictureSource:%1" ).arg( source.toString() ), 2 );
369 }
370 }
371
372 loadPicture( source );
373}
374
375void QgsLayoutItemPicture::loadRemotePicture( const QString &url )
376{
377 //remote location
378
380 QEventLoop loop;
381 connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
382 fetcher.fetchContent( QUrl( url ) );
383
384 //wait until picture fetched
385 loop.exec( QEventLoop::ExcludeUserInputEvents );
386
387 QNetworkReply *reply = fetcher.reply();
388 if ( reply )
389 {
390 QImageReader imageReader( reply );
391 imageReader.setAutoTransform( true );
392
393 if ( imageReader.format() == "pdf" )
394 {
395 // special handling for this format -- we need to pass the desired target size onto the image reader
396 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
397 // a very low resolution image (the driver assumes points == pixels!)
398 // For other image formats, we read the original image size only and defer resampling to later in this
399 // function. That gives us more control over the resampling method used.
400
401 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
402 const QSize sizeAt72Dpi = imageReader.size();
403 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
404 imageReader.setScaledSize( sizeAtTargetDpi );
405 }
406
407 mImage = imageReader.read();
409 }
410 else
411 {
413 }
414}
415
416void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
417{
418 QFile pic;
419 pic.setFileName( path );
420
421 if ( !pic.exists() )
422 {
424 }
425 else
426 {
427 const QFileInfo sourceFileInfo( pic );
428 const QString sourceFileSuffix = sourceFileInfo.suffix();
429 if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
430 {
431 //try to open svg
434 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
435 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
436 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
437
438 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
439 1.0, 0, false, evaluatedParameters );
440 mSVG.load( svgContent );
441 if ( mSVG.isValid() )
442 {
444 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
445 mDefaultSvgSize.setWidth( viewBox.width() );
446 mDefaultSvgSize.setHeight( viewBox.height() );
447 }
448 else
449 {
451 }
452 }
453 else
454 {
455 //try to open raster with QImageReader
456 QImageReader imageReader( pic.fileName() );
457 imageReader.setAutoTransform( true );
458
459 if ( imageReader.format() == "pdf" )
460 {
461 // special handling for this format -- we need to pass the desired target size onto the image reader
462 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
463 // a very low resolution image (the driver assumes points == pixels!)
464 // For other image formats, we read the original image size only and defer resampling to later in this
465 // function. That gives us more control over the resampling method used.
466
467 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
468 const QSize sizeAt72Dpi = imageReader.size();
469 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
470 imageReader.setScaledSize( sizeAtTargetDpi );
471 }
472
473 if ( imageReader.read( &mImage ) )
474 {
476 }
477 else
478 {
480 }
481 }
482 }
483}
484
485void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
486{
487 if ( path.isEmpty() )
488 {
489 mImage = QImage();
490 mSVG.load( QByteArray() );
491 return;
492 }
493
494 switch ( mMode )
495 {
497 break;
498
500 {
501 bool fitsInCache = false;
502 bool isMissing = false;
503 mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true, mLayout->renderContext().dpi(), -1, &isMissing );
504 if ( mImage.isNull() || isMissing )
506 break;
507 }
508
510 {
513 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
514 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
515
516 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
517
518 bool isMissingImage = false;
519 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
520 1.0, 0, false, evaluatedParameters, &isMissingImage );
521 mSVG.load( svgContent );
522 if ( mSVG.isValid() && !isMissingImage )
523 {
525 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
526 mDefaultSvgSize.setWidth( viewBox.width() );
527 mDefaultSvgSize.setHeight( viewBox.height() );
528 }
529 else
530 {
532 }
533 break;
534 }
535 }
536}
537
538void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
539{
540 setPictureRotation( rotation );
541 emit pictureRotationChanged( rotation );
542}
543
544void QgsLayoutItemPicture::loadPicture( const QVariant &data )
545{
546 mIsMissingImage = false;
547 QVariant imageData( data );
548 mEvaluatedPath = data.toString();
549
550 if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == Qgis::PictureFormat::Unknown )
551 {
552 const QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
553 imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
554 }
555
556 if ( imageData.userType() == QMetaType::Type::QByteArray )
557 {
558 if ( mImage.loadFromData( imageData.toByteArray() ) )
559 {
561 }
562 }
563 else if ( mMode == Qgis::PictureFormat::Unknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
564 {
565 //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
566 loadRemotePicture( mEvaluatedPath );
567 }
568 else if ( mMode == Qgis::PictureFormat::Unknown )
569 {
570 //local location - for old API/project compatibility only!!
571 loadLocalPicture( mEvaluatedPath );
572 }
573 else
574 {
575 loadPictureUsingCache( mEvaluatedPath );
576 }
577
578 if ( mMode != Qgis::PictureFormat::Unknown ) //make sure we start with a new QImage
579 {
581 }
582 else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
583 {
584 //trying to load an invalid file or bad expression, show cross picture
585 mIsMissingImage = true;
586 if ( mOriginalMode == Qgis::PictureFormat::Raster )
587 {
588 const QString badFile( QStringLiteral( ":/images/composer/missing_image.png" ) );
589 QImageReader imageReader( badFile );
590 if ( imageReader.read( &mImage ) )
592 }
593 else if ( mOriginalMode == Qgis::PictureFormat::SVG )
594 {
595 const QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
596 mSVG.load( badFile );
597 if ( mSVG.isValid() )
598 {
600 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
601 mDefaultSvgSize.setWidth( viewBox.width() );
602 mDefaultSvgSize.setHeight( viewBox.height() );
603 }
604 }
606 }
607
608 update();
609 emit changed();
610}
611
612QSizeF QgsLayoutItemPicture::pictureSize()
613{
614 if ( mMode == Qgis::PictureFormat::SVG )
615 {
616 return mDefaultSvgSize;
617 }
618 else if ( mMode == Qgis::PictureFormat::Raster )
619 {
620 return QSizeF( mImage.width(), mImage.height() );
621 }
622 else
623 {
624 return QSizeF( 0, 0 );
625 }
626}
627
629{
630 return mIsMissingImage;
631}
632
634{
635 return mEvaluatedPath;
636}
637
638QMap<QString, QgsProperty> QgsLayoutItemPicture::svgDynamicParameters() const
639{
640 const QVariantMap parameters = mCustomProperties.value( QStringLiteral( "svg-dynamic-parameters" ), QVariantMap() ).toMap();
641 return QgsProperty::variantMapToPropertyMap( parameters );
642}
643
644void QgsLayoutItemPicture::setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters )
645{
646 const QVariantMap variantMap = QgsProperty::propertyMapToVariantMap( parameters );
647 mCustomProperties.setValue( QStringLiteral( "svg-dynamic-parameters" ), variantMap );
649}
650
651void QgsLayoutItemPicture::shapeChanged()
652{
653 if ( mMode == Qgis::PictureFormat::SVG && !mLoadingSvg )
654 {
655 mLoadingSvg = true;
657 mLoadingSvg = false;
658 }
659}
660
662{
663 const double oldRotation = mPictureRotation;
664 mPictureRotation = rotation;
665
666 if ( mResizeMode == Zoom )
667 {
668 //find largest scaling of picture with this rotation which fits in item
669 const QSizeF currentPictureSize = pictureSize();
670 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
671 mPictureWidth = rotatedImageRect.width();
672 mPictureHeight = rotatedImageRect.height();
673 update();
674 }
675 else if ( mResizeMode == ZoomResizeFrame )
676 {
677 const QSizeF currentPictureSize = pictureSize();
678 const QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
679
680 //calculate actual size of image inside frame
681 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
682
683 //rotate image rect by new rotation and get bounding box
684 QTransform tr;
685 tr.rotate( mPictureRotation );
686 QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
687
688 //keep the center in the same location
689 newRect.moveCenter( oldRect.center() );
690 attemptSetSceneRect( newRect );
691 emit changed();
692 }
693
694 emit pictureRotationChanged( mPictureRotation );
695}
696
698{
699 mNorthArrowHandler->setLinkedMap( map );
700}
701
703{
704 mResizeMode = mode;
706 || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
707 {
708 //call set scene rect to force item to resize to fit picture
710 }
711 update();
712}
713
715{
716 //call set scene rect with current position/size, as this will trigger the
717 //picture item to recalculate its frame and image size
718 attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
719}
720
733
735{
736 mOriginalMode = format;
737 mMode = format;
738 mSourcePath = path;
740}
741
743{
744 return mSourcePath;
745}
746
747bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
748{
749 QString imagePath = mSourcePath;
750
751 // convert from absolute path to relative. For SVG we also need to consider system SVG paths
752 const QgsPathResolver pathResolver = context.pathResolver();
753 if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
754 imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
755 else
756 imagePath = pathResolver.writePath( imagePath );
757
758 elem.setAttribute( QStringLiteral( "file" ), imagePath );
759 elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
760 elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
761 elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
762 elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
763 elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsColorUtils::colorToString( mSvgFillColor ) );
764 elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsColorUtils::colorToString( mSvgStrokeColor ) );
765 elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
766 elem.setAttribute( QStringLiteral( "mode" ), static_cast< int >( mOriginalMode ) );
767
768 //rotation
769 elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
770 if ( !mNorthArrowHandler->linkedMap() )
771 {
772 elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
773 }
774 else
775 {
776 elem.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
777 }
778 elem.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
779 elem.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
780 return true;
781}
782
783bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
784{
785 mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
786 mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
787 mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
788 //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
789 mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
790
791 mSvgFillColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsColorUtils::colorToString( QColor( 255, 255, 255 ) ) ) );
792 mSvgStrokeColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsColorUtils::colorToString( QColor( 0, 0, 0 ) ) ) );
793 mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
794 mOriginalMode = static_cast< Qgis::PictureFormat >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( static_cast< int >( Qgis::PictureFormat::Unknown ) ) ).toInt() );
795 mMode = mOriginalMode;
796
797 const QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
798 if ( !composerItemList.isEmpty() )
799 {
800 const QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
801
802 if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
803 {
804 //in versions prior to 2.1 picture rotation was stored in the rotation attribute
805 mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
806 }
807 }
808
809 mDefaultSvgSize = QSize( 0, 0 );
810
811 if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
812 {
813 //update pre 2.5 picture expression to use data defined expression
814 const QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QString() );
815 const QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
816 bool expressionActive;
817 expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
818
820 }
821
822 QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
823
824 // convert from relative path to absolute. For SVG we also need to consider system SVG paths
825 const QgsPathResolver pathResolver = context.pathResolver();
826 if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
827 imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
828 else
829 imagePath = pathResolver.readPath( imagePath );
830
831 mSourcePath = imagePath;
832
833 //picture rotation
834 if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
835 {
836 mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
837 }
838
839 //rotation map
840 mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
841 mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
842
843 mNorthArrowHandler->setLinkedMap( nullptr );
844 mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
845
846 return true;
847}
848
850{
851 return mNorthArrowHandler->linkedMap();
852}
853
855{
856 return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
857}
858
863
865{
866 return mNorthArrowHandler->northOffset();
867}
868
870{
871 mNorthArrowHandler->setNorthOffset( offset );
872}
873
875{
876 mPictureAnchor = anchor;
877 update();
878}
879
880void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
881{
882 mSvgFillColor = color;
884}
885
887{
888 mSvgStrokeColor = color;
890}
891
893{
894 mSvgStrokeWidth = width;
896}
897
899{
900 if ( mOriginalMode == mode )
901 return;
902
903 mOriginalMode = mode;
904 mMode = mode;
906}
907
909{
910 if ( !mLayout || mRotationMapUuid.isEmpty() )
911 {
912 mNorthArrowHandler->setLinkedMap( nullptr );
913 }
914 else
915 {
916 mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
917 }
918
920}
@ Millimeters
Millimeters.
PictureFormat
Picture formats.
Definition qgis.h:4901
@ 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:5958
QMap< QString, QString > QgsStringMap
Definition qgis.h:6496
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39