QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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( QStringLiteral( "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( QStringLiteral( "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 
672 void QgsLayoutItemPicture::setPicturePath( const QString &path )
673 {
674  mSourcePath = path;
675  refreshPicture();
676 }
677 
679 {
680  return mSourcePath;
681 }
682 
683 bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
684 {
685  QString imagePath = mSourcePath;
686 
687  // convert from absolute path to relative. For SVG we also need to consider system SVG paths
688  QgsPathResolver pathResolver = context.pathResolver();
689  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
690  imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
691  else
692  imagePath = pathResolver.writePath( imagePath );
693 
694  elem.setAttribute( QStringLiteral( "file" ), imagePath );
695  elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
696  elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
697  elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
698  elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
699  elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
700  elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
701  elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
702 
703  //rotation
704  elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
705  if ( !mRotationMap )
706  {
707  elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
708  }
709  else
710  {
711  elem.setAttribute( QStringLiteral( "mapUuid" ), mRotationMap->uuid() );
712  }
713  elem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
714  elem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
715  return true;
716 }
717 
718 bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
719 {
720  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
721  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
722  mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
723  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
724  mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
725 
726  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
727  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
728  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
729 
730  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
731  if ( !composerItemList.isEmpty() )
732  {
733  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
734 
735  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
736  {
737  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
738  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
739  }
740  }
741 
742  mDefaultSvgSize = QSize( 0, 0 );
743 
744  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
745  {
746  //update pre 2.5 picture expression to use data defined expression
747  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QString() );
748  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
749  bool expressionActive;
750  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
751 
753  }
754 
755  QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
756 
757  // convert from relative path to absolute. For SVG we also need to consider system SVG paths
758  QgsPathResolver pathResolver = context.pathResolver();
759  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
760  imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
761  else
762  imagePath = pathResolver.readPath( imagePath );
763 
764  mSourcePath = imagePath;
765 
766  //picture rotation
767  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
768  {
769  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
770  }
771 
772  //rotation map
773  mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
774  mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
775 
776  disconnectMap( mRotationMap );
777  mRotationMap = nullptr;
778  mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
779 
780  return true;
781 }
782 
784 {
785  return mRotationMap;
786 }
787 
789 {
790  mNorthMode = mode;
791  updateMapRotation();
792 }
793 
795 {
796  mNorthOffset = offset;
797  updateMapRotation();
798 }
799 
801 {
802  mPictureAnchor = anchor;
803  update();
804 }
805 
806 void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
807 {
808  mSvgFillColor = color;
809  refreshPicture();
810 }
811 
812 void QgsLayoutItemPicture::setSvgStrokeColor( const QColor &color )
813 {
814  mSvgStrokeColor = color;
815  refreshPicture();
816 }
817 
819 {
820  mSvgStrokeWidth = width;
821  refreshPicture();
822 }
823 
825 {
826  if ( !mLayout || mRotationMapUuid.isEmpty() )
827  {
828  mRotationMap = nullptr;
829  }
830  else
831  {
832  if ( mRotationMap )
833  {
834  disconnectMap( mRotationMap );
835  }
836  if ( ( mRotationMap = qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) ) )
837  {
838  connect( mRotationMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemPicture::updateMapRotation );
839  connect( mRotationMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemPicture::updateMapRotation );
840  }
841  }
842 
843  refreshPicture();
844 }
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...
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QSizeF applyItemSizeConstraint(QSizeF targetSize) override
Applies any item-specific size constraint handling to a given targetSize in layout units...
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.
Lower left corner of item.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
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...
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:278
Stretches image to fit frame, ignores aspect ratio.
int type() const override
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
Upper center of item.
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
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.
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.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
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.
const QgsCoordinateReferenceSystem & crs
QgsLayoutItemPicture(QgsLayout *layout)
Constructor for QgsLayoutItemPicture, with the specified parent layout.
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:356
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.
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)
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.
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
Layout graphical items for displaying a map.
QgsPropertyCollection mDataDefinedProperties
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).
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
QPointer< QgsLayout > mLayout
void setLinkedMap(QgsLayoutItemMap *map)
Sets the map object for rotation.
void finished()
Emitted when content has loaded.
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.
Contains information about the context in which a coordinate transform is executed.
QString picturePath() const
Returns the path of the source image.
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.
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...
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:366
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 what() const
Definition: qgsexception.h:48
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.
QgsLayoutItemMap * linkedMap() const
Returns the linked rotation map, if set.
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.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
Format mode() const
Returns the current picture mode (image format).
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.
const QgsLayout * layout() const
Returns the layout the object is attached to.
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.
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.