QGIS API Documentation  2.99.0-Master (19b062c)
qgscomposerpicture.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerpicture.cpp
3  -------------------
4  begin : September 2005
5  copyright : (C) 2005 by Radim Blazek
6  email : [email protected]
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 "qgscomposerpicture.h"
19 #include "qgscomposerutils.h"
20 #include "qgscomposermap.h"
21 #include "qgscomposition.h"
22 #include "qgsatlascomposition.h"
23 #include "qgsproject.h"
24 #include "qgsexpression.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsmessagelog.h"
27 #include "qgspathresolver.h"
28 #include "qgsproperty.h"
30 #include "qgssymbollayerutils.h"
31 #include "qgssvgcache.h"
32 #include "qgslogger.h"
33 #include "qgsbearingutils.h"
34 #include "qgsmapsettings.h"
35 
36 #include <QDomDocument>
37 #include <QDomElement>
38 #include <QFileInfo>
39 #include <QImageReader>
40 #include <QPainter>
41 #include <QSvgRenderer>
42 #include <QNetworkRequest>
43 #include <QNetworkReply>
44 #include <QEventLoop>
45 #include <QCoreApplication>
46 
48  : QgsComposerItem( composition )
49 {
50  mPictureWidth = rect().width();
51  init();
52 }
53 
55  : QgsComposerItem( nullptr )
56 {
57  mPictureHeight = rect().height();
58  init();
59 }
60 
61 void QgsComposerPicture::init()
62 {
63  //default to no background
64  setBackgroundEnabled( false );
65 
66  //connect some signals
67 
68  //connect to atlas feature changing
69  //to update the picture source expression
71 
72  //connect to composer print resolution changing
74 }
75 
76 void QgsComposerPicture::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
77 {
78  Q_UNUSED( itemStyle );
79  Q_UNUSED( pWidget );
80  if ( !painter )
81  {
82  return;
83  }
84  if ( !shouldDrawItem() )
85  {
86  return;
87  }
88 
89  drawBackground( painter );
90 
91  //int newDpi = ( painter->device()->logicalDpiX() + painter->device()->logicalDpiY() ) / 2;
92 
93  //picture resizing
94  if ( mMode != Unknown )
95  {
96  double boundRectWidthMM;
97  double boundRectHeightMM;
98  QRect imageRect;
99  if ( mResizeMode == QgsComposerPicture::Zoom || mResizeMode == QgsComposerPicture::ZoomResizeFrame )
100  {
101  boundRectWidthMM = mPictureWidth;
102  boundRectHeightMM = mPictureHeight;
103  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
104  }
105  else if ( mResizeMode == QgsComposerPicture::Stretch )
106  {
107  boundRectWidthMM = rect().width();
108  boundRectHeightMM = rect().height();
109  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
110  }
111  else if ( mResizeMode == QgsComposerPicture::Clip )
112  {
113  boundRectWidthMM = rect().width();
114  boundRectHeightMM = rect().height();
115  int imageRectWidthPixels = mImage.width();
116  int imageRectHeightPixels = mImage.height();
117  imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
118  QSize( imageRectWidthPixels, imageRectHeightPixels ) );
119  }
120  else
121  {
122  boundRectWidthMM = rect().width();
123  boundRectHeightMM = rect().height();
124  imageRect = QRect( 0, 0, rect().width() * mComposition->printResolution() / 25.4,
125  rect().height() * mComposition->printResolution() / 25.4 );
126  }
127  painter->save();
128  //antialiasing on
129  painter->setRenderHint( QPainter::Antialiasing, true );
130 
131  //zoom mode - calculate anchor point and rotation
132  if ( mResizeMode == Zoom )
133  {
134  //TODO - allow placement modes with rotation set. for now, setting a rotation
135  //always places picture in center of frame
136  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
137  {
138  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
139  painter->rotate( mPictureRotation );
140  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
141  }
142  else
143  {
144  //shift painter to edge/middle of frame depending on placement
145  double diffX = rect().width() - boundRectWidthMM;
146  double diffY = rect().height() - boundRectHeightMM;
147 
148  double dX = 0;
149  double dY = 0;
150  switch ( mPictureAnchor )
151  {
152  case UpperLeft:
153  case MiddleLeft:
154  case LowerLeft:
155  //nothing to do
156  break;
157  case UpperMiddle:
158  case Middle:
159  case LowerMiddle:
160  dX = diffX / 2.0;
161  break;
162  case UpperRight:
163  case MiddleRight:
164  case LowerRight:
165  dX = diffX;
166  break;
167  }
168  switch ( mPictureAnchor )
169  {
170  case UpperLeft:
171  case UpperMiddle:
172  case UpperRight:
173  //nothing to do
174  break;
175  case MiddleLeft:
176  case Middle:
177  case MiddleRight:
178  dY = diffY / 2.0;
179  break;
180  case LowerLeft:
181  case LowerMiddle:
182  case LowerRight:
183  dY = diffY;
184  break;
185  }
186  painter->translate( dX, dY );
187  }
188  }
189  else if ( mResizeMode == ZoomResizeFrame )
190  {
191  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
192  {
193  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
194  painter->rotate( mPictureRotation );
195  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
196  }
197  }
198 
199  if ( mMode == SVG )
200  {
201  mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
202  }
203  else if ( mMode == RASTER )
204  {
205  painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
206  }
207 
208  painter->restore();
209  }
210 
211  //frame and selection boxes
212  drawFrame( painter );
213  if ( isSelected() )
214  {
215  drawSelectionBoxes( painter );
216  }
217 }
218 
219 QRect QgsComposerPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
220 {
221  int boundRectWidthPixels = boundRectWidthMM * mComposition->printResolution() / 25.4;
222  int boundRectHeightPixels = boundRectHeightMM * mComposition->printResolution() / 25.4;
223 
224  //update boundRectWidth/Height so that they exactly match pixel bounds
225  boundRectWidthMM = boundRectWidthPixels * 25.4 / mComposition->printResolution();
226  boundRectHeightMM = boundRectHeightPixels * 25.4 / mComposition->printResolution();
227 
228  //calculate part of image which fits in bounds
229  int leftClip = 0;
230  int topClip = 0;
231 
232  //calculate left crop
233  switch ( mPictureAnchor )
234  {
235  case UpperLeft:
236  case MiddleLeft:
237  case LowerLeft:
238  leftClip = 0;
239  break;
240  case UpperMiddle:
241  case Middle:
242  case LowerMiddle:
243  leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
244  break;
245  case UpperRight:
246  case MiddleRight:
247  case LowerRight:
248  leftClip = imageRectPixels.width() - boundRectWidthPixels;
249  break;
250  }
251 
252  //calculate top crop
253  switch ( mPictureAnchor )
254  {
255  case UpperLeft:
256  case UpperMiddle:
257  case UpperRight:
258  topClip = 0;
259  break;
260  case MiddleLeft:
261  case Middle:
262  case MiddleRight:
263  topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
264  break;
265  case LowerLeft:
266  case LowerMiddle:
267  case LowerRight:
268  topClip = imageRectPixels.height() - boundRectHeightPixels;
269  break;
270  }
271 
272  return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
273 }
274 
276 {
278  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
279 
280  QString source = mSourcePath;
281 
282  //data defined source set?
283  mHasExpressionError = false;
285  {
286  bool ok = false;
287  source = mDataDefinedProperties.valueAsString( QgsComposerObject::PictureSource, *evalContext, source, &ok );
288  if ( ok )
289  {
290  source = source.trimmed();
291  QgsDebugMsg( QString( "exprVal PictureSource:%1" ).arg( source ) );
292  }
293  else
294  {
295  mHasExpressionError = true;
296  source = QString();
297  QgsMessageLog::logMessage( tr( "Picture expression eval error" ) );
298  }
299  }
300 
301  loadPicture( source );
302 }
303 
304 void QgsComposerPicture::loadRemotePicture( const QString &url )
305 {
306  //remote location
307 
308  QgsNetworkContentFetcher fetcher;
309  //pause until HTML fetch
310  mLoaded = false;
311  fetcher.fetchContent( QUrl( url ) );
312  connect( &fetcher, &QgsNetworkContentFetcher::finished, this, &QgsComposerPicture::remotePictureLoaded );
313 
314  while ( !mLoaded )
315  {
316  qApp->processEvents();
317  }
318 
319  QNetworkReply *reply = fetcher.reply();
320  if ( reply )
321  {
322  QImageReader imageReader( reply );
323  mImage = imageReader.read();
324  mMode = RASTER;
325  reply->deleteLater();
326  }
327  else
328  {
329  mMode = Unknown;
330  }
331 }
332 
333 void QgsComposerPicture::loadLocalPicture( const QString &path )
334 {
335  QFile pic;
336  pic.setFileName( path );
337 
338  if ( !pic.exists() )
339  {
340  mMode = Unknown;
341  }
342  else
343  {
344  QFileInfo sourceFileInfo( pic );
345  QString sourceFileSuffix = sourceFileInfo.suffix();
346  if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
347  {
348  //try to open svg
350  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsComposerObject::PictureSvgBackgroundColor, context, mSvgFillColor );
351  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsComposerObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
352  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
353  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
354  1.0 );
355  mSVG.load( svgContent );
356  if ( mSVG.isValid() )
357  {
358  mMode = SVG;
359  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
360  mDefaultSvgSize.setWidth( viewBox.width() );
361  mDefaultSvgSize.setHeight( viewBox.height() );
362  }
363  else
364  {
365  mMode = Unknown;
366  }
367  }
368  else
369  {
370  //try to open raster with QImageReader
371  QImageReader imageReader( pic.fileName() );
372  if ( imageReader.read( &mImage ) )
373  {
374  mMode = RASTER;
375  }
376  else
377  {
378  mMode = Unknown;
379  }
380  }
381  }
382 
383 }
384 
385 void QgsComposerPicture::remotePictureLoaded()
386 {
387  mLoaded = true;
388 }
389 
390 void QgsComposerPicture::updateMapRotation()
391 {
392  if ( !mRotationMap )
393  return;
394 
395  // take map rotation
396  double rotation = mRotationMap->mapRotation();
397 
398  // handle true north
399  switch ( mNorthMode )
400  {
401  case GridNorth:
402  break; // nothing to do
403 
404  case TrueNorth:
405  {
406  QgsPointXY center = mRotationMap->currentMapExtent()->center();
407  QgsCoordinateReferenceSystem crs = mRotationMap->crs();
408 
409  try
410  {
411  double bearing = QgsBearingUtils::bearingTrueNorth( crs, center );
412  rotation += bearing;
413  }
414  catch ( QgsException &e )
415  {
416  Q_UNUSED( e );
417  QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
418  }
419  break;
420  }
421  }
422 
423  rotation += mNorthOffset;
424  setPictureRotation( rotation );
425 }
426 
427 void QgsComposerPicture::loadPicture( const QString &path )
428 {
429  if ( path.startsWith( QLatin1String( "http" ) ) )
430  {
431  //remote location
432  loadRemotePicture( path );
433  }
434  else
435  {
436  //local location
437  loadLocalPicture( path );
438  }
439  if ( mMode != Unknown ) //make sure we start with a new QImage
440  {
441  recalculateSize();
442  }
443  else if ( mHasExpressionError || !( path.isEmpty() ) )
444  {
445  //trying to load an invalid file or bad expression, show cross picture
446  mMode = SVG;
447  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
448  mSVG.load( badFile );
449  if ( mSVG.isValid() )
450  {
451  mMode = SVG;
452  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
453  mDefaultSvgSize.setWidth( viewBox.width() );
454  mDefaultSvgSize.setHeight( viewBox.height() );
455  recalculateSize();
456  }
457  }
458 
459  emit itemChanged();
460 }
461 
462 QRectF QgsComposerPicture::boundedImageRect( double deviceWidth, double deviceHeight )
463 {
464  double imageToDeviceRatio;
465  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
466  {
467  imageToDeviceRatio = deviceWidth / mImage.width();
468  double height = imageToDeviceRatio * mImage.height();
469  return QRectF( 0, 0, deviceWidth, height );
470  }
471  else
472  {
473  imageToDeviceRatio = deviceHeight / mImage.height();
474  double width = imageToDeviceRatio * mImage.width();
475  return QRectF( 0, 0, width, deviceHeight );
476  }
477 }
478 
479 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
480 {
481  double imageToSvgRatio;
482  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
483  {
484  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
485  double width = mDefaultSvgSize.width() * imageToSvgRatio;
486  return QRectF( 0, 0, width, deviceHeight );
487  }
488  else
489  {
490  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
491  double height = mDefaultSvgSize.height() * imageToSvgRatio;
492  return QRectF( 0, 0, deviceWidth, height );
493  }
494 }
495 
496 QSizeF QgsComposerPicture::pictureSize()
497 {
498  if ( mMode == SVG )
499  {
500  return mDefaultSvgSize;
501  }
502  else if ( mMode == RASTER )
503  {
504  return QSizeF( mImage.width(), mImage.height() );
505  }
506  else
507  {
508  return QSizeF( 0, 0 );
509  }
510 }
511 
512 #if 0
513 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
514 {
515  double imageToSvgRatio;
516  if ( deviceWidth / mDefaultSvgSize.width() < deviceHeight / mDefaultSvgSize.height() )
517  {
518  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
519  double height = mDefaultSvgSize.height() * imageToSvgRatio;
520  return QRectF( 0, 0, deviceWidth, height );
521  }
522  else
523  {
524  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
525  double width = mDefaultSvgSize.width() * imageToSvgRatio;
526  return QRectF( 0, 0, width, deviceHeight );
527  }
528 }
529 #endif //0
530 
531 void QgsComposerPicture::setSceneRect( const QRectF &rectangle )
532 {
533  QSizeF currentPictureSize = pictureSize();
534 
535  if ( mResizeMode == QgsComposerPicture::Clip )
536  {
537  QgsComposerItem::setSceneRect( rectangle );
538  mPictureWidth = rectangle.width();
539  mPictureHeight = rectangle.height();
540  }
541  else
542  {
543  QRectF newRect = rectangle;
544 
545  if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
546  {
547  QSizeF targetImageSize;
548  if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
549  {
550  targetImageSize = currentPictureSize;
551  }
552  else
553  {
554  //calculate aspect ratio of bounds of rotated image
555  QTransform tr;
556  tr.rotate( mPictureRotation );
557  QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
558  targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
559  }
560 
561  //if height has changed more than width, then fix width and set height correspondingly
562  //else, do the opposite
563  if ( std::fabs( rect().width() - rectangle.width() ) <
564  std::fabs( rect().height() - rectangle.height() ) )
565  {
566  newRect.setHeight( targetImageSize.height() * newRect.width() / targetImageSize.width() );
567  }
568  else
569  {
570  newRect.setWidth( targetImageSize.width() * newRect.height() / targetImageSize.height() );
571  }
572  }
573  else if ( mResizeMode == FrameToImageSize )
574  {
575  if ( !( currentPictureSize.isEmpty() ) )
576  {
577  newRect.setWidth( currentPictureSize.width() * 25.4 / mComposition->printResolution() );
578  newRect.setHeight( currentPictureSize.height() * 25.4 / mComposition->printResolution() );
579  }
580  }
581 
582  //find largest scaling of picture with this rotation which fits in item
583  if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
584  {
585  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), newRect, mPictureRotation );
586  mPictureWidth = rotatedImageRect.width();
587  mPictureHeight = rotatedImageRect.height();
588  }
589  else
590  {
591  mPictureWidth = newRect.width();
592  mPictureHeight = newRect.height();
593  }
594 
596  emit itemChanged();
597  }
598 
599  if ( mMode == SVG && !mLoadingSvg )
600  {
601  mLoadingSvg = true;
602  refreshPicture();
603  mLoadingSvg = false;
604  }
605 }
606 
608 {
609  double oldRotation = mPictureRotation;
610  mPictureRotation = rotation;
611 
612  if ( mResizeMode == Zoom )
613  {
614  //find largest scaling of picture with this rotation which fits in item
615  QSizeF currentPictureSize = pictureSize();
616  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
617  mPictureWidth = rotatedImageRect.width();
618  mPictureHeight = rotatedImageRect.height();
619  update();
620  }
621  else if ( mResizeMode == ZoomResizeFrame )
622  {
623  QSizeF currentPictureSize = pictureSize();
624  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
625 
626  //calculate actual size of image inside frame
627  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
628 
629  //rotate image rect by new rotation and get bounding box
630  QTransform tr;
631  tr.rotate( mPictureRotation );
632  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
633 
634  //keep the center in the same location
635  newRect.moveCenter( oldRect.center() );
637  emit itemChanged();
638  }
639 
640  emit pictureRotationChanged( mPictureRotation );
641 }
642 
643 void QgsComposerPicture::setRotationMap( int composerMapId )
644 {
645  if ( !mComposition )
646  {
647  return;
648  }
649 
650  if ( composerMapId == -1 ) //disable rotation from map
651  {
652  disconnect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
653  disconnect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
654  mRotationMap = nullptr;
655  }
656 
657  const QgsComposerMap *map = mComposition->getComposerMapById( composerMapId );
658  if ( !map )
659  {
660  return;
661  }
662  if ( mRotationMap )
663  {
664  disconnect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
665  disconnect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
666  }
667  mPictureRotation = map->mapRotation();
668  connect( map, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
669  connect( map, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
670  mRotationMap = map;
671  updateMapRotation();
672  emit pictureRotationChanged( mPictureRotation );
673 }
674 
676 {
677  mResizeMode = mode;
679  || ( mode == QgsComposerPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
680  {
681  //call set scene rect to force item to resize to fit picture
682  recalculateSize();
683  }
684  update();
685 }
686 
688 {
689  //call set scene rect with current position/size, as this will trigger the
690  //picture item to recalculate its frame and image size
691  setSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
692 }
693 
695 {
697  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
698 
701  || property == QgsComposerObject::AllProperties )
702  {
703  refreshPicture( evalContext );
704  }
705 
706  QgsComposerItem::refreshDataDefinedProperty( property, evalContext );
707 }
708 
709 void QgsComposerPicture::setPicturePath( const QString &path )
710 {
711  mSourcePath = path;
712  refreshPicture();
713 }
714 
716 {
717  return mSourcePath;
718 }
719 
720 bool QgsComposerPicture::writeXml( QDomElement &elem, QDomDocument &doc ) const
721 {
722  if ( elem.isNull() )
723  {
724  return false;
725  }
726  QDomElement composerPictureElem = doc.createElement( QStringLiteral( "ComposerPicture" ) );
727  QString imagePath = mSourcePath;
728  if ( mComposition )
729  {
730  // convert from absolute path to relative. For SVG we also need to consider system SVG paths
731  QgsPathResolver pathResolver = mComposition->project()->pathResolver();
732  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
733  imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
734  else
735  imagePath = pathResolver.writePath( imagePath );
736  }
737  composerPictureElem.setAttribute( QStringLiteral( "file" ), imagePath );
738  composerPictureElem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
739  composerPictureElem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
740  composerPictureElem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
741  composerPictureElem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
742  composerPictureElem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
743  composerPictureElem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
744  composerPictureElem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
745 
746  //rotation
747  composerPictureElem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
748  if ( !mRotationMap )
749  {
750  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), -1 );
751  }
752  else
753  {
754  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), mRotationMap->id() );
755  }
756  composerPictureElem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
757  composerPictureElem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
758 
759  _writeXml( composerPictureElem, doc );
760  elem.appendChild( composerPictureElem );
761  return true;
762 }
763 
764 bool QgsComposerPicture::readXml( const QDomElement &itemElem, const QDomDocument &doc )
765 {
766  if ( itemElem.isNull() )
767  {
768  return false;
769  }
770 
771  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
772  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
773  mResizeMode = QgsComposerPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
774  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
775  mPictureAnchor = static_cast< QgsComposerItem::ItemPositionMode >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsComposerItem::Middle ) ).toInt() );
776 
777  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
778  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
779  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
780 
781  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
782  if ( !composerItemList.isEmpty() )
783  {
784  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
785 
786  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
787  {
788  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
789  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
790  }
791 
792  _readXml( composerItemElem, doc );
793  }
794 
795  mDefaultSvgSize = QSize( 0, 0 );
796 
797  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
798  {
799  //update pre 2.5 picture expression to use data defined expression
800  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QLatin1String( "" ) );
801  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
802  bool expressionActive;
803  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
804 
806  }
807 
808  QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
809  if ( mComposition )
810  {
811  // convert from relative path to absolute. For SVG we also need to consider system SVG paths
812  QgsPathResolver pathResolver = mComposition->project()->pathResolver();
813  if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
814  imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
815  else
816  imagePath = pathResolver.readPath( imagePath );
817  }
818  mSourcePath = imagePath;
819 
820  //picture rotation
821  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
822  {
823  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
824  }
825 
826  //rotation map
827  mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
828  mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
829 
830  int rotationMapId = itemElem.attribute( QStringLiteral( "mapId" ), QStringLiteral( "-1" ) ).toInt();
831  if ( rotationMapId == -1 )
832  {
833  mRotationMap = nullptr;
834  }
835  else if ( mComposition )
836  {
837 
838  if ( mRotationMap )
839  {
840  disconnect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
841  disconnect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
842  }
843  mRotationMap = mComposition->getComposerMapById( rotationMapId );
844  connect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
845  connect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
846  }
847 
848  refreshPicture();
849 
850  emit itemChanged();
851  return true;
852 }
853 
855 {
856  if ( !mRotationMap )
857  {
858  return -1;
859  }
860  else
861  {
862  return mRotationMap->id();
863  }
864 }
865 
867 {
868  mNorthMode = mode;
869  updateMapRotation();
870 }
871 
873 {
874  mNorthOffset = offset;
875  updateMapRotation();
876 }
877 
879 {
880  mPictureAnchor = anchor;
881  update();
882 }
883 
884 void QgsComposerPicture::setSvgFillColor( const QColor &color )
885 {
886  mSvgFillColor = color;
887  refreshPicture();
888 }
889 
890 void QgsComposerPicture::setSvgStrokeColor( const QColor &color )
891 {
892  mSvgStrokeColor = color;
893  refreshPicture();
894 }
895 
897 {
898  mSvgStrokeWidth = width;
899  refreshPicture();
900 }
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
Enlarges image to fit frame while maintaining aspect ratio of picture.
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
void setSvgStrokeWidth(double width)
Sets the stroke width used for parametrized SVG files.
const QgsComposerMap * getComposerMapById(const int id) const
Returns the composer map with specified id.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
static QString svgSymbolPathToName(QString path, const QgsPathResolver &pathResolver)
Get SVG symbols&#39;s name from its path.
Stretches image to fit frame, ignores aspect ratio.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
void itemChanged()
Emitted when the item changes.
QgsComposerPicture(QgsComposition *composition)
A class to represent a 2D point.
Definition: qgspointxy.h:43
int printResolution() const
A item that forms part of a map composition.
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
int id() const
Get identification number.
void mapRotationChanged(double newRotation)
Is emitted on rotation change to notify north arrow pictures.
Sets size of frame to match original size of image without scaling.
virtual void drawFrame(QPainter *p)
Draw black frame around item.
QNetworkReply * reply()
Returns a reference to the network reply.
static QString svgSymbolNameToPath(QString name, const QgsPathResolver &pathResolver)
Get SVG symbol&#39;s path from its name.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:227
DataDefinedProperty
Data defined properties for different item types.
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 recalculateSize()
Forces a recalculation of the picture&#39;s frame size.
void setSvgFillColor(const QColor &color)
Sets the fill color used for parametrized SVG files.
static QString encodeColor(const QColor &color)
int rotationMap() const
Returns the id of the rotation map.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as a color...
void setPictureAnchor(QgsComposerItem::ItemPositionMode anchor)
Sets the picture&#39;s anchor point, which controls how it is placed within the picture item&#39;s frame...
bool _writeXml(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document. Usually called from writeXml methods of ...
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
Reimplementation of QCanvasItem::paint.
ResizeMode
Controls how pictures are scaled within the item&#39;s frame.
NorthMode
Method for syncing rotation to a map&#39;s North direction.
QgsPathResolver pathResolver() const
Return path resolver object with considering whether the project uses absolute or relative paths and ...
HTTP network content fetcher.
virtual void drawSelectionBoxes(QPainter *p)
Draws additional graphics on selected items.
virtual void setResizeMode(ResizeMode mode)
Sets the resize mode used for drawing the picture within the item bounds.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setNorthMode(NorthMode mode)
Sets the mode used to align the picture to a map&#39;s North.
static QRectF largestRotatedRectWithinBounds(const QRectF &originalRect, const QRectF &boundsRect, const double rotation)
Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by a specified amount.
virtual void refreshDataDefinedProperty(const QgsComposerObject::DataDefinedProperty property=QgsComposerObject::AllProperties, const QgsExpressionContext *context=nullptr) override
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
void setNorthOffset(double offset)
Sets the offset added to the picture&#39;s rotation from a map&#39;s North.
void finished()
Emitted when content has loaded.
QgsPropertyCollection mDataDefinedProperties
static double bearingTrueNorth(const QgsCoordinateReferenceSystem &crs, const QgsPointXY &point)
Returns the direction to true north from a specified point and for a specified coordinate reference s...
bool _readXml(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document. Usually called from readXml methods of su...
void setBackgroundEnabled(const bool drawBackground)
Set whether this item has a Background drawn around it or not.
virtual void refreshDataDefinedProperty(const QgsComposerObject::DataDefinedProperty property=QgsComposerObject::AllProperties, const QgsExpressionContext *context=nullptr) override
Graphics scene for map printing.
Object representing map window.
Enlarges image to fit frame, then resizes frame to fit resultant image.
void featureChanged(QgsFeature *feature)
Is emitted when the current atlas feature changes.
Draws image at original size and clips any portion which falls outside frame.
void pictureRotationChanged(double newRotation)
Is emitted on picture rotation change.
virtual QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the item&#39;s current state.
QgsComposition * mComposition
static void logMessage(const QString &message, const QString &tag=QString(), MessageLevel level=QgsMessageLog::WARNING)
add a message to the instance (and create it if necessary)
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
QgsProject * project() const
The project associated with the composition.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as a string...
virtual void drawBackground(QPainter *p)
Draw background.
Mode mode() const
Returns the current picture mode (image format).
This class represents a coordinate reference system (CRS).
virtual void setSceneRect(const QRectF &rectangle)
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0)
Get SVG content.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
void setSceneRect(const QRectF &rectangle) override
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
virtual void setPictureRotation(double rotation)
Sets the picture rotation within the item bounds, in degrees clockwise.
QgsAtlasComposition & atlasComposition()
void setRotationMap(int composerMapId)
Sets the map object for rotation (by id).
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as a double...
void setPicturePath(const QString &path)
Sets the source path of the image (may be svg or a raster format).
void extentChanged()
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:166
QgsComposerItem(QgsComposition *composition, bool manageZValue=true)
Constructor.
void printResolutionChanged()
Is emitted when the compositions print resolution changes.
double mapRotation(QgsComposerObject::PropertyValueType valueType=QgsComposerObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the composer item, in degrees clockwise...
Resolves relative paths into absolute paths and vice versa.
void fetchContent(const QUrl &url)
Fetches content from a remote URL and handles redirects.
bool writeXml(QDomElement &elem, QDomDocument &doc) const override
Stores state in Dom element.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
Defines a QGIS exception class.
Definition: qgsexception.h:34
const QgsRectangle * currentMapExtent() const
Returns a pointer to the current map extent, which is either the original user specified extent or th...
void refreshPicture(const QgsExpressionContext *context=nullptr)
Recalculates the source image (if using an expression for picture&#39;s source) and reloads and redraws t...
static QColor decodeColor(const QString &str)
QString picturePath() const
Returns the path of the source image.
All properties for item.