QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
18 #include "qgslayoutitempicture.h"
19 #include "qgslayoutitemregistry.h"
20 #include "qgslayout.h"
21 #include "qgslayoutrendercontext.h"
22 #include "qgslayoutreportcontext.h"
23 #include "qgslayoutitemmap.h"
24 #include "qgslayoututils.h"
25 #include "qgsproject.h"
26 #include "qgsexpression.h"
27 #include "qgsvectorlayer.h"
28 #include "qgsmessagelog.h"
29 #include "qgspathresolver.h"
30 #include "qgsproperty.h"
32 #include "qgssymbollayerutils.h"
33 #include "qgssvgcache.h"
34 #include "qgslogger.h"
35 #include "qgsbearingutils.h"
36 #include "qgsmapsettings.h"
37 #include "qgsreadwritecontext.h"
38 #include "qgsimagecache.h"
40 
41 #include <QDomDocument>
42 #include <QDomElement>
43 #include <QFileInfo>
44 #include <QImageReader>
45 #include <QPainter>
46 #include <QSvgRenderer>
47 #include <QNetworkRequest>
48 #include <QNetworkReply>
49 #include <QEventLoop>
50 #include <QCoreApplication>
51 #include <QUrl>
52 
54  : QgsLayoutItem( layout )
55  , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
56 {
57  //default to no background
58  setBackgroundEnabled( false );
59 
60  //connect some signals
61 
62  //connect to atlas feature changing
63  //to update the picture source expression
64  connect( &layout->reportContext(), &QgsLayoutReportContext::changed, this, [ = ] { refreshPicture(); } );
65 
66  //connect to layout print resolution changing
68 
69  connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
70  connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemPicture::updateNorthArrowRotation );
71 }
72 
74 {
76 }
77 
79 {
80  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPicture.svg" ) );
81 }
82 
84 {
85  return new QgsLayoutItemPicture( layout );
86 }
87 
89 {
90  QPainter *painter = context.renderContext().painter();
91  QgsScopedQPainterState painterState( painter );
92  // painter is scaled to dots, so scale back to layout units
93  painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
94 
95  //picture resizing
96  if ( mMode != FormatUnknown )
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  int imageRectWidthPixels = mImage.width();
118  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(), QgsUnitTypes::LayoutMillimeters ).length() * mLayout->renderContext().dpi() / 25.4,
127  mLayout->convertFromLayoutUnits( rect().height(), QgsUnitTypes::LayoutMillimeters ).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  double diffX = rect().width() - boundRectWidthMM;
145  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 == FormatSVG )
199  {
200  mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
201  }
202  else if ( mMode == FormatRaster )
203  {
204  painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
205  }
206 
207  }
208 }
209 
210 QSizeF QgsLayoutItemPicture::applyItemSizeConstraint( const QSizeF targetSize )
211 {
212  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  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  QgsLayoutSize sizeMM = mLayout->convertFromLayoutUnits( currentPictureSize, QgsUnitTypes::LayoutMillimeters );
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  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 
282 QRect QgsLayoutItemPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
283 {
284  int boundRectWidthPixels = boundRectWidthMM * mLayout->renderContext().dpi() / 25.4;
285  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 {
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  {
351  mMode = FormatUnknown;
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  QgsMessageLog::logMessage( tr( "Picture expression eval error" ) );
360  }
361  else if ( source.type() != QVariant::ByteArray )
362  {
363  source = source.toString().trimmed();
364  QgsDebugMsgLevel( QStringLiteral( "exprVal PictureSource:%1" ).arg( source.toString() ), 2 );
365  }
366  }
367 
368  loadPicture( source );
369 }
370 
371 void QgsLayoutItemPicture::loadRemotePicture( const QString &url )
372 {
373  //remote location
374 
375  QgsNetworkContentFetcher fetcher;
376  QEventLoop loop;
377  connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
378  fetcher.fetchContent( QUrl( url ) );
379 
380  //wait until picture fetched
381  loop.exec( QEventLoop::ExcludeUserInputEvents );
382 
383  QNetworkReply *reply = fetcher.reply();
384  if ( reply )
385  {
386  QImageReader imageReader( reply );
387  mImage = imageReader.read();
388  mMode = FormatRaster;
389  }
390  else
391  {
392  mMode = FormatUnknown;
393  }
394 }
395 
396 void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
397 {
398  QFile pic;
399  pic.setFileName( path );
400 
401  if ( !pic.exists() )
402  {
403  mMode = FormatUnknown;
404  }
405  else
406  {
407  QFileInfo sourceFileInfo( pic );
408  QString sourceFileSuffix = sourceFileInfo.suffix();
409  if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
410  {
411  //try to open svg
413  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
414  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
415  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
417 
418  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
419  1.0, 0, false, evaluatedParameters );
420  mSVG.load( svgContent );
421  if ( mSVG.isValid() )
422  {
423  mMode = FormatSVG;
424  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
425  mDefaultSvgSize.setWidth( viewBox.width() );
426  mDefaultSvgSize.setHeight( viewBox.height() );
427  }
428  else
429  {
430  mMode = FormatUnknown;
431  }
432  }
433  else
434  {
435  //try to open raster with QImageReader
436  QImageReader imageReader( pic.fileName() );
437  if ( imageReader.read( &mImage ) )
438  {
439  mMode = FormatRaster;
440  }
441  else
442  {
443  mMode = FormatUnknown;
444  }
445  }
446  }
447 }
448 
449 void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
450 {
451  if ( path.isEmpty() )
452  {
453  mImage = QImage();
454  return;
455  }
456 
457  switch ( mMode )
458  {
459  case FormatUnknown:
460  break;
461 
462  case FormatRaster:
463  {
464  bool fitsInCache = false;
465  bool isMissing = false;
466  mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true, &isMissing );
467  if ( mImage.isNull() || isMissing )
468  mMode = FormatUnknown;
469  break;
470  }
471 
472  case FormatSVG:
473  {
475  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
476  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
477  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
478 
480 
481  bool isMissingImage = false;
482  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
483  1.0, 0, false, evaluatedParameters, &isMissingImage );
484  mSVG.load( svgContent );
485  if ( mSVG.isValid() && !isMissingImage )
486  {
487  mMode = FormatSVG;
488  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
489  mDefaultSvgSize.setWidth( viewBox.width() );
490  mDefaultSvgSize.setHeight( viewBox.height() );
491  }
492  else
493  {
494  mMode = FormatUnknown;
495  }
496  break;
497  }
498  }
499 }
500 
501 void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
502 {
503  setPictureRotation( rotation );
504  emit pictureRotationChanged( rotation );
505 }
506 
507 void QgsLayoutItemPicture::loadPicture( const QVariant &data )
508 {
509  mIsMissingImage = false;
510  QVariant imageData( data );
511  mEvaluatedPath = data.toString();
512 
513  if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == FormatUnknown )
514  {
515  QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
516  imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
517  }
518 
519  if ( imageData.type() == QVariant::ByteArray )
520  {
521  if ( mImage.loadFromData( imageData.toByteArray() ) )
522  {
523  mMode = FormatRaster;
524  }
525  }
526  else if ( mMode == FormatUnknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
527  {
528  //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
529  loadRemotePicture( mEvaluatedPath );
530  }
531  else if ( mMode == FormatUnknown )
532  {
533  //local location - for old API/project compatibility only!!
534  loadLocalPicture( mEvaluatedPath );
535  }
536  else
537  {
538  loadPictureUsingCache( mEvaluatedPath );
539  }
540 
541  if ( mMode != FormatUnknown ) //make sure we start with a new QImage
542  {
543  recalculateSize();
544  }
545  else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
546  {
547  //trying to load an invalid file or bad expression, show cross picture
548  mMode = FormatSVG;
549  mIsMissingImage = true;
550  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
551  mSVG.load( badFile );
552  if ( mSVG.isValid() )
553  {
554  mMode = FormatSVG;
555  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
556  mDefaultSvgSize.setWidth( viewBox.width() );
557  mDefaultSvgSize.setHeight( viewBox.height() );
558  recalculateSize();
559  }
560  }
561 
562  update();
563  emit changed();
564 }
565 
566 QRectF QgsLayoutItemPicture::boundedImageRect( double deviceWidth, double deviceHeight )
567 {
568  double imageToDeviceRatio;
569  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
570  {
571  imageToDeviceRatio = deviceWidth / mImage.width();
572  double height = imageToDeviceRatio * mImage.height();
573  return QRectF( 0, 0, deviceWidth, height );
574  }
575  else
576  {
577  imageToDeviceRatio = deviceHeight / mImage.height();
578  double width = imageToDeviceRatio * mImage.width();
579  return QRectF( 0, 0, width, deviceHeight );
580  }
581 }
582 
583 QRectF QgsLayoutItemPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
584 {
585  double imageToSvgRatio;
586  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
587  {
588  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
589  double width = mDefaultSvgSize.width() * imageToSvgRatio;
590  return QRectF( 0, 0, width, deviceHeight );
591  }
592  else
593  {
594  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
595  double height = mDefaultSvgSize.height() * imageToSvgRatio;
596  return QRectF( 0, 0, deviceWidth, height );
597  }
598 }
599 
600 QSizeF QgsLayoutItemPicture::pictureSize()
601 {
602  if ( mMode == FormatSVG )
603  {
604  return mDefaultSvgSize;
605  }
606  else if ( mMode == FormatRaster )
607  {
608  return QSizeF( mImage.width(), mImage.height() );
609  }
610  else
611  {
612  return QSizeF( 0, 0 );
613  }
614 }
615 
617 {
618  return mIsMissingImage;
619 }
620 
622 {
623  return mEvaluatedPath;
624 }
625 
626 QMap<QString, QgsProperty> QgsLayoutItemPicture::svgDynamicParameters() const
627 {
628  const QVariantMap parameters = mCustomProperties.value( QStringLiteral( "svg-dynamic-parameters" ), QVariantMap() ).toMap();
629  return QgsProperty::variantMapToPropertyMap( parameters );
630 }
631 
632 void QgsLayoutItemPicture::setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters )
633 {
634  const QVariantMap variantMap = QgsProperty::propertyMapToVariantMap( parameters );
635  mCustomProperties.setValue( QStringLiteral( "svg-dynamic-parameters" ), variantMap );
636  refreshPicture();
637 }
638 
639 void QgsLayoutItemPicture::shapeChanged()
640 {
641  if ( mMode == FormatSVG && !mLoadingSvg )
642  {
643  mLoadingSvg = true;
644  refreshPicture();
645  mLoadingSvg = false;
646  }
647 }
648 
650 {
651  double oldRotation = mPictureRotation;
652  mPictureRotation = rotation;
653 
654  if ( mResizeMode == Zoom )
655  {
656  //find largest scaling of picture with this rotation which fits in item
657  QSizeF currentPictureSize = pictureSize();
658  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
659  mPictureWidth = rotatedImageRect.width();
660  mPictureHeight = rotatedImageRect.height();
661  update();
662  }
663  else if ( mResizeMode == ZoomResizeFrame )
664  {
665  QSizeF currentPictureSize = pictureSize();
666  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
667 
668  //calculate actual size of image inside frame
669  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
670 
671  //rotate image rect by new rotation and get bounding box
672  QTransform tr;
673  tr.rotate( mPictureRotation );
674  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
675 
676  //keep the center in the same location
677  newRect.moveCenter( oldRect.center() );
678  attemptSetSceneRect( newRect );
679  emit changed();
680  }
681 
682  emit pictureRotationChanged( mPictureRotation );
683 }
684 
686 {
687  mNorthArrowHandler->setLinkedMap( map );
688 }
689 
691 {
692  mResizeMode = mode;
694  || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
695  {
696  //call set scene rect to force item to resize to fit picture
697  recalculateSize();
698  }
699  update();
700 }
701 
703 {
704  //call set scene rect with current position/size, as this will trigger the
705  //picture item to recalculate its frame and image size
706  attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
707 }
708 
710 {
713  || property == QgsLayoutObject::AllProperties )
714  {
716  refreshPicture( &context );
717  }
718 
720 }
721 
722 void QgsLayoutItemPicture::setPicturePath( const QString &path, Format format )
723 {
724  mMode = format;
725  mSourcePath = path;
726  refreshPicture();
727 }
728 
730 {
731  return mSourcePath;
732 }
733 
734 bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
735 {
736  QString imagePath = mSourcePath;
737 
738  // convert from absolute path to relative. For SVG we also need to consider system SVG paths
739  QgsPathResolver pathResolver = context.pathResolver();
740  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
741  imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
742  else
743  imagePath = pathResolver.writePath( imagePath );
744 
745  elem.setAttribute( QStringLiteral( "file" ), imagePath );
746  elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
747  elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
748  elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
749  elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
750  elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
751  elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
752  elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
753  elem.setAttribute( QStringLiteral( "mode" ), mMode );
754 
755  //rotation
756  elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
757  if ( !mNorthArrowHandler->linkedMap() )
758  {
759  elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
760  }
761  else
762  {
763  elem.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
764  }
765  elem.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
766  elem.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
767  return true;
768 }
769 
770 bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
771 {
772  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
773  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
774  mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
775  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
776  mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
777 
778  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
779  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
780  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
781  mMode = static_cast< Format >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( FormatUnknown ) ).toInt() );
782 
783  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
784  if ( !composerItemList.isEmpty() )
785  {
786  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
787 
788  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
789  {
790  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
791  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
792  }
793  }
794 
795  mDefaultSvgSize = QSize( 0, 0 );
796 
797  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
798  {
799  //update pre 2.5 picture expression to use data defined expression
800  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QString() );
801  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
802  bool expressionActive;
803  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
804 
806  }
807 
808  QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
809 
810  // convert from relative path to absolute. For SVG we also need to consider system SVG paths
811  QgsPathResolver pathResolver = context.pathResolver();
812  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
813  imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
814  else
815  imagePath = pathResolver.readPath( imagePath );
816 
817  mSourcePath = imagePath;
818 
819  //picture rotation
820  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
821  {
822  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
823  }
824 
825  //rotation map
826  mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
827  mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
828 
829  mNorthArrowHandler->setLinkedMap( nullptr );
830  mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
831 
832  return true;
833 }
834 
836 {
837  return mNorthArrowHandler->linkedMap();
838 }
839 
841 {
842  return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
843 }
844 
846 {
847  mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( mode ) );
848 }
849 
851 {
852  return mNorthArrowHandler->northOffset();
853 }
854 
856 {
857  mNorthArrowHandler->setNorthOffset( offset );
858 }
859 
861 {
862  mPictureAnchor = anchor;
863  update();
864 }
865 
866 void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
867 {
868  mSvgFillColor = color;
869  refreshPicture();
870 }
871 
872 void QgsLayoutItemPicture::setSvgStrokeColor( const QColor &color )
873 {
874  mSvgStrokeColor = color;
875  refreshPicture();
876 }
877 
879 {
880  mSvgStrokeWidth = width;
881  refreshPicture();
882 }
883 
885 {
886  if ( mMode == mode )
887  return;
888 
889  mMode = mode;
890  refreshPicture();
891 }
892 
894 {
895  if ( !mLayout || mRotationMapUuid.isEmpty() )
896  {
897  mNorthArrowHandler->setLinkedMap( nullptr );
898  }
899  else
900  {
901  mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
902  }
903 
904  refreshPicture();
905 }
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...
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, 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 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 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 setPicturePath(const QString &path, Format format=FormatUnknown)
Sets the source path of the image (may be svg or a raster format).
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.
NorthMode
Method for syncing rotation to a map's North direction.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
Format mode() const
Returns the current picture mode (image format).
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::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...
void setMode(Format mode)
Sets the current picture mode (image format).
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.
Format
Format of source image.
@ FormatUnknown
Invalid or unknown image type.
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.
Definition: qgslayoutitem.h:45
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
Base class for graphical items within a QgsLayout.
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...
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
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.
@ AllProperties
All properties for item.
@ PictureSvgStrokeWidth
SVG stroke width.
@ PictureSvgBackgroundColor
SVG background color.
@ PictureSource
Picture source url.
void dpiChanged()
Emitted when the context's DPI is changed.
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...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
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:51
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
QgsLayoutReportContext & reportContext()
Returns a reference to the layout's report context, which stores information relating to the current ...
Definition: qgslayout.cpp:369
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.
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const override
Prepares the collection against a specified expression context.
A store for object properties.
Definition: qgsproperty.h:232
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 QColor decodeColor(const QString &str)
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.
static QString encodeColor(const QColor &color)
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
QMap< QString, QString > QgsStringMap
Definition: qgis.h:1041
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39