QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
39 #include <QDomDocument>
40 #include <QDomElement>
41 #include <QFileInfo>
42 #include <QImageReader>
43 #include <QPainter>
44 #include <QSvgRenderer>
45 #include <QNetworkRequest>
46 #include <QNetworkReply>
47 #include <QEventLoop>
48 #include <QCoreApplication>
49 
51  : QgsLayoutItem( layout )
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, [ = ] { refreshPicture(); } );
61 
62  //connect to layout print resolution changing
64 
65  connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
66 }
67 
69 {
71 }
72 
74 {
75  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPicture.svg" ) );
76 }
77 
79 {
80  return new QgsLayoutItemPicture( layout );
81 }
82 
84 {
85  QPainter *painter = context.renderContext().painter();
86  painter->save();
87  // painter is scaled to dots, so scale back to layout units
88  painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
89 
90  //picture resizing
91  if ( mMode != FormatUnknown )
92  {
93  double boundRectWidthMM;
94  double boundRectHeightMM;
95  QRect imageRect;
96  if ( mResizeMode == QgsLayoutItemPicture::Zoom || mResizeMode == QgsLayoutItemPicture::ZoomResizeFrame )
97  {
98  boundRectWidthMM = mPictureWidth;
99  boundRectHeightMM = mPictureHeight;
100  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
101  }
102  else if ( mResizeMode == QgsLayoutItemPicture::Stretch )
103  {
104  boundRectWidthMM = rect().width();
105  boundRectHeightMM = rect().height();
106  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
107  }
108  else if ( mResizeMode == QgsLayoutItemPicture::Clip )
109  {
110  boundRectWidthMM = rect().width();
111  boundRectHeightMM = rect().height();
112  int imageRectWidthPixels = mImage.width();
113  int imageRectHeightPixels = mImage.height();
114  imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
115  QSize( imageRectWidthPixels, imageRectHeightPixels ) );
116  }
117  else
118  {
119  boundRectWidthMM = rect().width();
120  boundRectHeightMM = rect().height();
121  imageRect = QRect( 0, 0, mLayout->convertFromLayoutUnits( rect().width(), QgsUnitTypes::LayoutMillimeters ).length() * mLayout->renderContext().dpi() / 25.4,
122  mLayout->convertFromLayoutUnits( rect().height(), QgsUnitTypes::LayoutMillimeters ).length() * mLayout->renderContext().dpi() / 25.4 );
123  }
124 
125  //zoom mode - calculate anchor point and rotation
126  if ( mResizeMode == Zoom )
127  {
128  //TODO - allow placement modes with rotation set. for now, setting a rotation
129  //always places picture in center of frame
130  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
131  {
132  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
133  painter->rotate( mPictureRotation );
134  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
135  }
136  else
137  {
138  //shift painter to edge/middle of frame depending on placement
139  double diffX = rect().width() - boundRectWidthMM;
140  double diffY = rect().height() - boundRectHeightMM;
141 
142  double dX = 0;
143  double dY = 0;
144  switch ( mPictureAnchor )
145  {
146  case UpperLeft:
147  case MiddleLeft:
148  case LowerLeft:
149  //nothing to do
150  break;
151  case UpperMiddle:
152  case Middle:
153  case LowerMiddle:
154  dX = diffX / 2.0;
155  break;
156  case UpperRight:
157  case MiddleRight:
158  case LowerRight:
159  dX = diffX;
160  break;
161  }
162  switch ( mPictureAnchor )
163  {
164  case UpperLeft:
165  case UpperMiddle:
166  case UpperRight:
167  //nothing to do
168  break;
169  case MiddleLeft:
170  case Middle:
171  case MiddleRight:
172  dY = diffY / 2.0;
173  break;
174  case LowerLeft:
175  case LowerMiddle:
176  case LowerRight:
177  dY = diffY;
178  break;
179  }
180  painter->translate( dX, dY );
181  }
182  }
183  else if ( mResizeMode == ZoomResizeFrame )
184  {
185  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
186  {
187  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
188  painter->rotate( mPictureRotation );
189  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
190  }
191  }
192 
193  if ( mMode == FormatSVG )
194  {
195  mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
196  }
197  else if ( mMode == FormatRaster )
198  {
199  painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
200  }
201 
202  }
203  painter->restore();
204 }
205 
206 QSizeF QgsLayoutItemPicture::applyItemSizeConstraint( const QSizeF &targetSize )
207 {
208  QSizeF currentPictureSize = pictureSize();
209  QSizeF newSize = targetSize;
210  if ( mResizeMode == QgsLayoutItemPicture::Clip )
211  {
212  mPictureWidth = targetSize.width();
213  mPictureHeight = targetSize.height();
214  }
215  else
216  {
217  if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
218  {
219  QSizeF targetImageSize;
220  if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
221  {
222  targetImageSize = currentPictureSize;
223  }
224  else
225  {
226  //calculate aspect ratio of bounds of rotated image
227  QTransform tr;
228  tr.rotate( mPictureRotation );
229  QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
230  targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
231  }
232 
233  //if height has changed more than width, then fix width and set height correspondingly
234  //else, do the opposite
235  if ( std::fabs( rect().width() - targetSize.width() ) <
236  std::fabs( rect().height() - targetSize.height() ) )
237  {
238  newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
239  }
240  else
241  {
242  newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
243  }
244  }
245  else if ( mResizeMode == FrameToImageSize )
246  {
247  if ( !( currentPictureSize.isEmpty() ) )
248  {
249  QgsLayoutSize sizeMM = mLayout->convertFromLayoutUnits( currentPictureSize, QgsUnitTypes::LayoutMillimeters );
250  newSize.setWidth( sizeMM.width() * 25.4 / mLayout->renderContext().dpi() );
251  newSize.setHeight( sizeMM.height() * 25.4 / mLayout->renderContext().dpi() );
252  }
253  }
254 
255  //find largest scaling of picture with this rotation which fits in item
256  if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
257  {
258  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ),
259  QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
260  mPictureWidth = rotatedImageRect.width();
261  mPictureHeight = rotatedImageRect.height();
262  }
263  else
264  {
265  mPictureWidth = newSize.width();
266  mPictureHeight = newSize.height();
267  }
268 
269  if ( newSize != targetSize )
270  {
271  emit changed();
272  }
273  }
274 
275  return newSize;
276 }
277 
278 QRect QgsLayoutItemPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
279 {
280  int boundRectWidthPixels = boundRectWidthMM * mLayout->renderContext().dpi() / 25.4;
281  int boundRectHeightPixels = boundRectHeightMM * mLayout->renderContext().dpi() / 25.4;
282 
283  //update boundRectWidth/Height so that they exactly match pixel bounds
284  boundRectWidthMM = boundRectWidthPixels * 25.4 / mLayout->renderContext().dpi();
285  boundRectHeightMM = boundRectHeightPixels * 25.4 / mLayout->renderContext().dpi();
286 
287  //calculate part of image which fits in bounds
288  int leftClip = 0;
289  int topClip = 0;
290 
291  //calculate left crop
292  switch ( mPictureAnchor )
293  {
294  case UpperLeft:
295  case MiddleLeft:
296  case LowerLeft:
297  leftClip = 0;
298  break;
299  case UpperMiddle:
300  case Middle:
301  case LowerMiddle:
302  leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
303  break;
304  case UpperRight:
305  case MiddleRight:
306  case LowerRight:
307  leftClip = imageRectPixels.width() - boundRectWidthPixels;
308  break;
309  }
310 
311  //calculate top crop
312  switch ( mPictureAnchor )
313  {
314  case UpperLeft:
315  case UpperMiddle:
316  case UpperRight:
317  topClip = 0;
318  break;
319  case MiddleLeft:
320  case Middle:
321  case MiddleRight:
322  topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
323  break;
324  case LowerLeft:
325  case LowerMiddle:
326  case LowerRight:
327  topClip = imageRectPixels.height() - boundRectHeightPixels;
328  break;
329  }
330 
331  return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
332 }
333 
335 {
337  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
338 
339  QString source = mSourcePath;
340 
341  //data defined source set?
342  mHasExpressionError = false;
344  {
345  bool ok = false;
346  source = mDataDefinedProperties.valueAsString( QgsLayoutObject::PictureSource, *evalContext, source, &ok );
347  if ( ok )
348  {
349  source = source.trimmed();
350  QgsDebugMsg( QString( "exprVal PictureSource:%1" ).arg( source ) );
351  }
352  else
353  {
354  mHasExpressionError = true;
355  source = QString();
356  QgsMessageLog::logMessage( tr( "Picture expression eval error" ) );
357  }
358  }
359 
360  loadPicture( source );
361 }
362 
363 void QgsLayoutItemPicture::loadRemotePicture( const QString &url )
364 {
365  //remote location
366 
367  QgsNetworkContentFetcher fetcher;
368  QEventLoop loop;
369  connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
370  fetcher.fetchContent( QUrl( url ) );
371 
372  //wait until picture fetched
373  loop.exec( QEventLoop::ExcludeUserInputEvents );
374 
375  QNetworkReply *reply = fetcher.reply();
376  if ( reply )
377  {
378  QImageReader imageReader( reply );
379  mImage = imageReader.read();
380  mMode = FormatRaster;
381  }
382  else
383  {
384  mMode = FormatUnknown;
385  }
386 }
387 
388 void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
389 {
390  QFile pic;
391  pic.setFileName( path );
392 
393  if ( !pic.exists() )
394  {
395  mMode = FormatUnknown;
396  }
397  else
398  {
399  QFileInfo sourceFileInfo( pic );
400  QString sourceFileSuffix = sourceFileInfo.suffix();
401  if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
402  {
403  //try to open svg
405  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
406  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
407  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
408  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
409  1.0 );
410  mSVG.load( svgContent );
411  if ( mSVG.isValid() )
412  {
413  mMode = FormatSVG;
414  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
415  mDefaultSvgSize.setWidth( viewBox.width() );
416  mDefaultSvgSize.setHeight( viewBox.height() );
417  }
418  else
419  {
420  mMode = FormatUnknown;
421  }
422  }
423  else
424  {
425  //try to open raster with QImageReader
426  QImageReader imageReader( pic.fileName() );
427  if ( imageReader.read( &mImage ) )
428  {
429  mMode = FormatRaster;
430  }
431  else
432  {
433  mMode = FormatUnknown;
434  }
435  }
436  }
437 }
438 
439 void QgsLayoutItemPicture::disconnectMap( QgsLayoutItemMap *map )
440 {
441  if ( map )
442  {
443  disconnect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
444  disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
445  }
446 }
447 
448 void QgsLayoutItemPicture::updateMapRotation()
449 {
450  if ( !mRotationMap )
451  return;
452 
453  // take map rotation
454  double rotation = mRotationMap->mapRotation();
455 
456  // handle true north
457  switch ( mNorthMode )
458  {
459  case GridNorth:
460  break; // nothing to do
461 
462  case TrueNorth:
463  {
464  QgsPointXY center = mRotationMap->extent().center();
465  QgsCoordinateReferenceSystem crs = mRotationMap->crs();
466  QgsCoordinateTransformContext transformContext = mLayout->project()->transformContext();
467 
468  try
469  {
470  double bearing = QgsBearingUtils::bearingTrueNorth( crs, transformContext, center );
471  rotation += bearing;
472  }
473  catch ( QgsException &e )
474  {
475  Q_UNUSED( e );
476  QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
477  }
478  break;
479  }
480  }
481 
482  rotation += mNorthOffset;
483  setPictureRotation( rotation );
484 }
485 
486 void QgsLayoutItemPicture::loadPicture( const QString &path )
487 {
488  if ( path.startsWith( QLatin1String( "http" ) ) )
489  {
490  //remote location
491  loadRemotePicture( path );
492  }
493  else
494  {
495  //local location
496  loadLocalPicture( path );
497  }
498  if ( mMode != FormatUnknown ) //make sure we start with a new QImage
499  {
500  recalculateSize();
501  }
502  else if ( mHasExpressionError || !( path.isEmpty() ) )
503  {
504  //trying to load an invalid file or bad expression, show cross picture
505  mMode = FormatSVG;
506  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
507  mSVG.load( badFile );
508  if ( mSVG.isValid() )
509  {
510  mMode = FormatSVG;
511  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
512  mDefaultSvgSize.setWidth( viewBox.width() );
513  mDefaultSvgSize.setHeight( viewBox.height() );
514  recalculateSize();
515  }
516  }
517 
518  update();
519  emit changed();
520 }
521 
522 QRectF QgsLayoutItemPicture::boundedImageRect( double deviceWidth, double deviceHeight )
523 {
524  double imageToDeviceRatio;
525  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
526  {
527  imageToDeviceRatio = deviceWidth / mImage.width();
528  double height = imageToDeviceRatio * mImage.height();
529  return QRectF( 0, 0, deviceWidth, height );
530  }
531  else
532  {
533  imageToDeviceRatio = deviceHeight / mImage.height();
534  double width = imageToDeviceRatio * mImage.width();
535  return QRectF( 0, 0, width, deviceHeight );
536  }
537 }
538 
539 QRectF QgsLayoutItemPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
540 {
541  double imageToSvgRatio;
542  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
543  {
544  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
545  double width = mDefaultSvgSize.width() * imageToSvgRatio;
546  return QRectF( 0, 0, width, deviceHeight );
547  }
548  else
549  {
550  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
551  double height = mDefaultSvgSize.height() * imageToSvgRatio;
552  return QRectF( 0, 0, deviceWidth, height );
553  }
554 }
555 
556 QSizeF QgsLayoutItemPicture::pictureSize()
557 {
558  if ( mMode == FormatSVG )
559  {
560  return mDefaultSvgSize;
561  }
562  else if ( mMode == FormatRaster )
563  {
564  return QSizeF( mImage.width(), mImage.height() );
565  }
566  else
567  {
568  return QSizeF( 0, 0 );
569  }
570 }
571 
572 void QgsLayoutItemPicture::shapeChanged()
573 {
574  if ( mMode == FormatSVG && !mLoadingSvg )
575  {
576  mLoadingSvg = true;
577  refreshPicture();
578  mLoadingSvg = false;
579  }
580 }
581 
583 {
584  double oldRotation = mPictureRotation;
585  mPictureRotation = rotation;
586 
587  if ( mResizeMode == Zoom )
588  {
589  //find largest scaling of picture with this rotation which fits in item
590  QSizeF currentPictureSize = pictureSize();
591  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
592  mPictureWidth = rotatedImageRect.width();
593  mPictureHeight = rotatedImageRect.height();
594  update();
595  }
596  else if ( mResizeMode == ZoomResizeFrame )
597  {
598  QSizeF currentPictureSize = pictureSize();
599  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
600 
601  //calculate actual size of image inside frame
602  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
603 
604  //rotate image rect by new rotation and get bounding box
605  QTransform tr;
606  tr.rotate( mPictureRotation );
607  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
608 
609  //keep the center in the same location
610  newRect.moveCenter( oldRect.center() );
611  attemptSetSceneRect( newRect );
612  emit changed();
613  }
614 
615  emit pictureRotationChanged( mPictureRotation );
616 }
617 
619 {
620  if ( mRotationMap )
621  {
622  disconnectMap( mRotationMap );
623  }
624 
625  if ( !map ) //disable rotation from map
626  {
627  mRotationMap = nullptr;
628  }
629  else
630  {
631  mPictureRotation = map->mapRotation();
632  connect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
633  connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
634  mRotationMap = map;
635  updateMapRotation();
636  emit pictureRotationChanged( mPictureRotation );
637  }
638 }
639 
641 {
642  mResizeMode = mode;
644  || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
645  {
646  //call set scene rect to force item to resize to fit picture
647  recalculateSize();
648  }
649  update();
650 }
651 
653 {
654  //call set scene rect with current position/size, as this will trigger the
655  //picture item to recalculate its frame and image size
656  attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
657 }
658 
660 {
663  || property == QgsLayoutObject::AllProperties )
664  {
666  refreshPicture( &context );
667  }
668 
670 }
671 
673 {
675  return true;
676 
677  return mMode == FormatSVG && itemOpacity() < 1.0;
678 }
679 
680 void QgsLayoutItemPicture::setPicturePath( const QString &path )
681 {
682  mSourcePath = path;
683  refreshPicture();
684 }
685 
687 {
688  return mSourcePath;
689 }
690 
691 bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
692 {
693  QString imagePath = mSourcePath;
694 
695  // convert from absolute path to relative. For SVG we also need to consider system SVG paths
696  QgsPathResolver pathResolver = context.pathResolver();
697  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
698  imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
699  else
700  imagePath = pathResolver.writePath( imagePath );
701 
702  elem.setAttribute( QStringLiteral( "file" ), imagePath );
703  elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
704  elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
705  elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
706  elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
707  elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
708  elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
709  elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
710 
711  //rotation
712  elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
713  if ( !mRotationMap )
714  {
715  elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
716  }
717  else
718  {
719  elem.setAttribute( QStringLiteral( "mapUuid" ), mRotationMap->uuid() );
720  }
721  elem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
722  elem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
723  return true;
724 }
725 
726 bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
727 {
728  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
729  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
730  mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
731  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
732  mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
733 
734  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
735  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
736  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
737 
738  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
739  if ( !composerItemList.isEmpty() )
740  {
741  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
742 
743  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
744  {
745  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
746  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
747  }
748  }
749 
750  mDefaultSvgSize = QSize( 0, 0 );
751 
752  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
753  {
754  //update pre 2.5 picture expression to use data defined expression
755  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QLatin1String( "" ) );
756  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
757  bool expressionActive;
758  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
759 
761  }
762 
763  QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
764 
765  // convert from relative path to absolute. For SVG we also need to consider system SVG paths
766  QgsPathResolver pathResolver = context.pathResolver();
767  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
768  imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
769  else
770  imagePath = pathResolver.readPath( imagePath );
771 
772  mSourcePath = imagePath;
773 
774  //picture rotation
775  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
776  {
777  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
778  }
779 
780  //rotation map
781  mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
782  mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
783 
784  disconnectMap( mRotationMap );
785  mRotationMap = nullptr;
786  mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
787 
788  return true;
789 }
790 
792 {
793  return mRotationMap;
794 }
795 
797 {
798  mNorthMode = mode;
799  updateMapRotation();
800 }
801 
803 {
804  mNorthOffset = offset;
805  updateMapRotation();
806 }
807 
809 {
810  mPictureAnchor = anchor;
811  update();
812 }
813 
814 void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
815 {
816  mSvgFillColor = color;
817  refreshPicture();
818 }
819 
820 void QgsLayoutItemPicture::setSvgStrokeColor( const QColor &color )
821 {
822  mSvgStrokeColor = color;
823  refreshPicture();
824 }
825 
827 {
828  mSvgStrokeWidth = width;
829  refreshPicture();
830 }
831 
833 {
834  if ( !mLayout || mRotationMapUuid.isEmpty() )
835  {
836  mRotationMap = nullptr;
837  }
838  else
839  {
840  if ( mRotationMap )
841  {
842  disconnectMap( mRotationMap );
843  }
844  if ( ( mRotationMap = qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) ) )
845  {
846  connect( mRotationMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
847  connect( mRotationMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
848  }
849  }
850 
851  refreshPicture();
852 }
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
The class is used as a container of context for various read/write operations on other objects...
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
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...
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QString picturePath() const
Returns the path of the source image.
static double bearingTrueNorth(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext, const QgsPointXY &point)
Returns the direction to true north from a specified point and for a specified coordinate reference s...
Base class for graphical items within a QgsLayout.
virtual bool containsAdvancedEffects() const
Returns true if the item contains contents with blend modes or transparency effects which can only be...
Lower left corner of item.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
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...
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
A class to represent a 2D point.
Definition: qgspointxy.h:43
void changed()
Emitted certain settings in the context is changed, e.g.
void extentChanged()
Is emitted when the map&#39;s extent changes.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:251
Stretches image to fit frame, ignores aspect ratio.
int type() const override
Upper center of item.
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
A layout item subclass that displays SVG files or raster format images (jpg, png, ...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
void setNorthOffset(double offset)
Sets the offset added to the picture&#39;s rotation from a map&#39;s North.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Format mode() const
Returns the current picture mode (image format).
void setSvgFillColor(const QColor &color)
Sets the fill color used for parametrized SVG files.
QNetworkReply * reply()
Returns a reference to the network reply.
static QgsLayoutItemPicture * create(QgsLayout *layout)
Returns a new picture item for the specified layout.
void setPictureAnchor(QgsLayoutItem::ReferencePoint anchor)
Sets the picture&#39;s anchor point, which controls how it is placed within the picture item&#39;s frame...
Lower right corner of item.
QgsLayoutItemPicture(QgsLayout *layout)
Constructor for QgsLayoutItemPicture, with the specified parent layout.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:355
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
void setPictureRotation(double rotation)
Sets the picture rotation within the item bounds, in degrees clockwise.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
QString what() const
Definition: qgsexception.h:48
void setSvgStrokeColor(const QColor &color)
Sets the stroke color used for parametrized SVG files.
void dpiChanged()
Emitted when the context&#39;s DPI is changed.
static QString encodeColor(const QColor &color)
bool containsAdvancedEffects() const override
ReferencePoint
Fixed position reference point.
void sizePositionChanged()
Emitted when the item&#39;s size or position changes.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s name from its path.
Invalid or unknown image type.
Layout graphical items for displaying a map.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
Upper right corner of item.
HTTP network content fetcher.
Lower center of item.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item&#39;s position and size to match the passed rect in layout coordinates...
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:71
Middle right of item.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QPointer< QgsLayout > mLayout
void setLinkedMap(QgsLayoutItemMap *map)
Sets the map object for rotation.
void finished()
Emitted when content has loaded.
double itemOpacity() const
Returns the item&#39;s opacity.
void pictureRotationChanged(double newRotation)
Is emitted on picture rotation change.
void setPicturePath(const QString &path)
Sets the source path of the image (may be svg or a raster format).
Upper left corner of item.
QSizeF applyItemSizeConstraint(const QSizeF &targetSize) override
Applies any item-specific size constraint handling to a given targetSize in layout units...
Contains information about the context in which a coordinate transform is executed.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void mapRotationChanged(double newRotation)
Is emitted when the map&#39;s rotation changes.
void recalculateSize()
Forces a recalculation of the picture&#39;s frame size.
void setSvgStrokeWidth(double width)
Sets the stroke width (in layout units) used for parametrized SVG files.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
Middle left of item.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:43
Enlarges image to fit frame, then resizes frame to fit resultant image.
QgsLayoutReportContext & reportContext()
Returns a reference to the layout&#39;s report context, which stores information relating to the current ...
Definition: qgslayout.cpp:365
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string...
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
NorthMode
Method for syncing rotation to a map&#39;s North direction.
QPainter * painter()
Returns the destination QPainter for the render operation.
Center of item.
static QRectF largestRotatedRectWithinBounds(const QRectF &originalRect, const QRectF &boundsRect, double rotation)
Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by the a specified rotation amount.
ResizeMode
Controls how pictures are scaled within the item&#39;s frame.
void setResizeMode(QgsLayoutItemPicture::ResizeMode mode)
Sets the resize mode used for drawing the picture within the item bounds.
This class represents a coordinate reference system (CRS).
Sets size of frame to match original size of image without scaling.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0)
Gets SVG content.
Draws image at original size and clips any portion which falls outside frame.
void setNorthMode(NorthMode mode)
Sets the mode used to align the picture to a map&#39;s North.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item&#39;s contents using the specified item render context.
void refreshPicture(const QgsExpressionContext *context=nullptr)
Recalculates the source image (if using an expression for picture&#39;s source) and reloads and redraws t...
QIcon icon() const override
Returns the item&#39;s icon.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s path from its name.
Enlarges image to fit frame while maintaining aspect ratio of picture.
Resolves relative paths into absolute paths and vice versa.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
void fetchContent(const QUrl &url)
Fetches content from a remote URL and handles redirects.
void changed()
Emitted when the object&#39;s properties change.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
QgsLayoutItemMap * linkedMap() const
Returns the linked rotation map, if set.
DataDefinedProperty
Data defined properties for different item types.
Defines a QGIS exception class.
Definition: qgsexception.h:34
static QColor decodeColor(const QString &str)
All properties for item.
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76