QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 );
416  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
417  1.0 );
418  mSVG.load( svgContent );
419  if ( mSVG.isValid() )
420  {
421  mMode = FormatSVG;
422  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
423  mDefaultSvgSize.setWidth( viewBox.width() );
424  mDefaultSvgSize.setHeight( viewBox.height() );
425  }
426  else
427  {
428  mMode = FormatUnknown;
429  }
430  }
431  else
432  {
433  //try to open raster with QImageReader
434  QImageReader imageReader( pic.fileName() );
435  if ( imageReader.read( &mImage ) )
436  {
437  mMode = FormatRaster;
438  }
439  else
440  {
441  mMode = FormatUnknown;
442  }
443  }
444  }
445 }
446 
447 void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
448 {
449  if ( path.isEmpty() )
450  {
451  mImage = QImage();
452  return;
453  }
454 
455  switch ( mMode )
456  {
457  case FormatUnknown:
458  break;
459 
460  case FormatRaster:
461  {
462  bool fitsInCache = false;
463  bool isMissing = false;
464  mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true, &isMissing );
465  if ( mImage.isNull() || isMissing )
466  mMode = FormatUnknown;
467  break;
468  }
469 
470  case FormatSVG:
471  {
473  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
474  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
475  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
476  // TODO parameters (handle this in the gui part)
477  QMap<QString, QString> parameters;
478  bool isMissingImage = false;
479  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
480  1.0, 0, false, parameters, &isMissingImage );
481  mSVG.load( svgContent );
482  if ( mSVG.isValid() && !isMissingImage )
483  {
484  mMode = FormatSVG;
485  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
486  mDefaultSvgSize.setWidth( viewBox.width() );
487  mDefaultSvgSize.setHeight( viewBox.height() );
488  }
489  else
490  {
491  mMode = FormatUnknown;
492  }
493  break;
494  }
495  }
496 }
497 
498 void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
499 {
500  setPictureRotation( rotation );
501  emit pictureRotationChanged( rotation );
502 }
503 
504 void QgsLayoutItemPicture::loadPicture( const QVariant &data )
505 {
506  mIsMissingImage = false;
507  QVariant imageData( data );
508  mEvaluatedPath = data.toString();
509 
510  if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == FormatUnknown )
511  {
512  QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
513  imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
514  }
515 
516  if ( imageData.type() == QVariant::ByteArray )
517  {
518  if ( mImage.loadFromData( imageData.toByteArray() ) )
519  {
520  mMode = FormatRaster;
521  }
522  }
523  else if ( mMode == FormatUnknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
524  {
525  //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
526  loadRemotePicture( mEvaluatedPath );
527  }
528  else if ( mMode == FormatUnknown )
529  {
530  //local location - for old API/project compatibility only!!
531  loadLocalPicture( mEvaluatedPath );
532  }
533  else
534  {
535  loadPictureUsingCache( mEvaluatedPath );
536  }
537 
538  if ( mMode != FormatUnknown ) //make sure we start with a new QImage
539  {
540  recalculateSize();
541  }
542  else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
543  {
544  //trying to load an invalid file or bad expression, show cross picture
545  mMode = FormatSVG;
546  mIsMissingImage = true;
547  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
548  mSVG.load( badFile );
549  if ( mSVG.isValid() )
550  {
551  mMode = FormatSVG;
552  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
553  mDefaultSvgSize.setWidth( viewBox.width() );
554  mDefaultSvgSize.setHeight( viewBox.height() );
555  recalculateSize();
556  }
557  }
558 
559  update();
560  emit changed();
561 }
562 
563 QRectF QgsLayoutItemPicture::boundedImageRect( double deviceWidth, double deviceHeight )
564 {
565  double imageToDeviceRatio;
566  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
567  {
568  imageToDeviceRatio = deviceWidth / mImage.width();
569  double height = imageToDeviceRatio * mImage.height();
570  return QRectF( 0, 0, deviceWidth, height );
571  }
572  else
573  {
574  imageToDeviceRatio = deviceHeight / mImage.height();
575  double width = imageToDeviceRatio * mImage.width();
576  return QRectF( 0, 0, width, deviceHeight );
577  }
578 }
579 
580 QRectF QgsLayoutItemPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
581 {
582  double imageToSvgRatio;
583  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
584  {
585  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
586  double width = mDefaultSvgSize.width() * imageToSvgRatio;
587  return QRectF( 0, 0, width, deviceHeight );
588  }
589  else
590  {
591  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
592  double height = mDefaultSvgSize.height() * imageToSvgRatio;
593  return QRectF( 0, 0, deviceWidth, height );
594  }
595 }
596 
597 QSizeF QgsLayoutItemPicture::pictureSize()
598 {
599  if ( mMode == FormatSVG )
600  {
601  return mDefaultSvgSize;
602  }
603  else if ( mMode == FormatRaster )
604  {
605  return QSizeF( mImage.width(), mImage.height() );
606  }
607  else
608  {
609  return QSizeF( 0, 0 );
610  }
611 }
612 
614 {
615  return mIsMissingImage;
616 }
617 
619 {
620  return mEvaluatedPath;
621 }
622 
623 void QgsLayoutItemPicture::shapeChanged()
624 {
625  if ( mMode == FormatSVG && !mLoadingSvg )
626  {
627  mLoadingSvg = true;
628  refreshPicture();
629  mLoadingSvg = false;
630  }
631 }
632 
634 {
635  double oldRotation = mPictureRotation;
636  mPictureRotation = rotation;
637 
638  if ( mResizeMode == Zoom )
639  {
640  //find largest scaling of picture with this rotation which fits in item
641  QSizeF currentPictureSize = pictureSize();
642  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
643  mPictureWidth = rotatedImageRect.width();
644  mPictureHeight = rotatedImageRect.height();
645  update();
646  }
647  else if ( mResizeMode == ZoomResizeFrame )
648  {
649  QSizeF currentPictureSize = pictureSize();
650  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
651 
652  //calculate actual size of image inside frame
653  QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
654 
655  //rotate image rect by new rotation and get bounding box
656  QTransform tr;
657  tr.rotate( mPictureRotation );
658  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
659 
660  //keep the center in the same location
661  newRect.moveCenter( oldRect.center() );
662  attemptSetSceneRect( newRect );
663  emit changed();
664  }
665 
666  emit pictureRotationChanged( mPictureRotation );
667 }
668 
670 {
671  mNorthArrowHandler->setLinkedMap( map );
672 }
673 
675 {
676  mResizeMode = mode;
678  || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
679  {
680  //call set scene rect to force item to resize to fit picture
681  recalculateSize();
682  }
683  update();
684 }
685 
687 {
688  //call set scene rect with current position/size, as this will trigger the
689  //picture item to recalculate its frame and image size
690  attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
691 }
692 
694 {
697  || property == QgsLayoutObject::AllProperties )
698  {
700  refreshPicture( &context );
701  }
702 
704 }
705 
706 void QgsLayoutItemPicture::setPicturePath( const QString &path, Format format )
707 {
708  mMode = format;
709  mSourcePath = path;
710  refreshPicture();
711 }
712 
714 {
715  return mSourcePath;
716 }
717 
718 bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
719 {
720  QString imagePath = mSourcePath;
721 
722  // convert from absolute path to relative. For SVG we also need to consider system SVG paths
723  QgsPathResolver pathResolver = context.pathResolver();
724  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
725  imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
726  else
727  imagePath = pathResolver.writePath( imagePath );
728 
729  elem.setAttribute( QStringLiteral( "file" ), imagePath );
730  elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
731  elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
732  elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
733  elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
734  elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
735  elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
736  elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
737  elem.setAttribute( QStringLiteral( "mode" ), mMode );
738 
739  //rotation
740  elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
741  if ( !mNorthArrowHandler->linkedMap() )
742  {
743  elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
744  }
745  else
746  {
747  elem.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
748  }
749  elem.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
750  elem.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
751  return true;
752 }
753 
754 bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
755 {
756  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
757  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
758  mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
759  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
760  mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
761 
762  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
763  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
764  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
765  mMode = static_cast< Format >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( FormatUnknown ) ).toInt() );
766 
767  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
768  if ( !composerItemList.isEmpty() )
769  {
770  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
771 
772  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
773  {
774  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
775  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
776  }
777  }
778 
779  mDefaultSvgSize = QSize( 0, 0 );
780 
781  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
782  {
783  //update pre 2.5 picture expression to use data defined expression
784  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QString() );
785  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
786  bool expressionActive;
787  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
788 
790  }
791 
792  QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
793 
794  // convert from relative path to absolute. For SVG we also need to consider system SVG paths
795  QgsPathResolver pathResolver = context.pathResolver();
796  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
797  imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
798  else
799  imagePath = pathResolver.readPath( imagePath );
800 
801  mSourcePath = imagePath;
802 
803  //picture rotation
804  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
805  {
806  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
807  }
808 
809  //rotation map
810  mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
811  mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
812 
813  mNorthArrowHandler->setLinkedMap( nullptr );
814  mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
815 
816  return true;
817 }
818 
820 {
821  return mNorthArrowHandler->linkedMap();
822 }
823 
825 {
826  return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
827 }
828 
830 {
831  mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( mode ) );
832 }
833 
835 {
836  return mNorthArrowHandler->northOffset();
837 }
838 
840 {
841  mNorthArrowHandler->setNorthOffset( offset );
842 }
843 
845 {
846  mPictureAnchor = anchor;
847  update();
848 }
849 
850 void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
851 {
852  mSvgFillColor = color;
853  refreshPicture();
854 }
855 
856 void QgsLayoutItemPicture::setSvgStrokeColor( const QColor &color )
857 {
858  mSvgStrokeColor = color;
859  refreshPicture();
860 }
861 
863 {
864  mSvgStrokeWidth = width;
865  refreshPicture();
866 }
867 
869 {
870  if ( mMode == mode )
871  return;
872 
873  mMode = mode;
874  refreshPicture();
875 }
876 
878 {
879  if ( !mLayout || mRotationMapUuid.isEmpty() )
880  {
881  mNorthArrowHandler->setLinkedMap( nullptr );
882  }
883  else
884  {
885  mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
886  }
887 
888  refreshPicture();
889 }
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 QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
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.
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.
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:50
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::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.
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
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.
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 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:182
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39