QGIS API Documentation  2.99.0-Master (01468d0)
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  , mMode( Unknown )
50  , mPictureRotation( 0 )
51  , mNorthMode( GridNorth )
52  , mNorthOffset( 0.0 )
53  , mResizeMode( QgsComposerPicture::Zoom )
54  , mPictureAnchor( UpperLeft )
55  , mHasExpressionError( false )
56  , mLoadingSvg( false )
57 {
58  mPictureWidth = rect().width();
59  init();
60 }
61 
63  : QgsComposerItem( nullptr )
64  , mMode( Unknown )
65  , mPictureRotation( 0 )
66  , mNorthMode( GridNorth )
67  , mNorthOffset( 0.0 )
68  , mResizeMode( QgsComposerPicture::Zoom )
69  , mPictureAnchor( UpperLeft )
70  , mHasExpressionError( false )
71  , mLoadingSvg( false )
72 {
73  mPictureHeight = rect().height();
74  init();
75 }
76 
77 void QgsComposerPicture::init()
78 {
79  //default to no background
80  setBackgroundEnabled( false );
81 
82  //connect some signals
83 
84  //connect to atlas feature changing
85  //to update the picture source expression
87 
88  //connect to composer print resolution changing
90 }
91 
92 void QgsComposerPicture::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
93 {
94  Q_UNUSED( itemStyle );
95  Q_UNUSED( pWidget );
96  if ( !painter )
97  {
98  return;
99  }
100  if ( !shouldDrawItem() )
101  {
102  return;
103  }
104 
105  drawBackground( painter );
106 
107  //int newDpi = ( painter->device()->logicalDpiX() + painter->device()->logicalDpiY() ) / 2;
108 
109  //picture resizing
110  if ( mMode != Unknown )
111  {
112  double boundRectWidthMM;
113  double boundRectHeightMM;
114  QRect imageRect;
115  if ( mResizeMode == QgsComposerPicture::Zoom || mResizeMode == QgsComposerPicture::ZoomResizeFrame )
116  {
117  boundRectWidthMM = mPictureWidth;
118  boundRectHeightMM = mPictureHeight;
119  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
120  }
121  else if ( mResizeMode == QgsComposerPicture::Stretch )
122  {
123  boundRectWidthMM = rect().width();
124  boundRectHeightMM = rect().height();
125  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
126  }
127  else if ( mResizeMode == QgsComposerPicture::Clip )
128  {
129  boundRectWidthMM = rect().width();
130  boundRectHeightMM = rect().height();
131  int imageRectWidthPixels = mImage.width();
132  int imageRectHeightPixels = mImage.height();
133  imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
134  QSize( imageRectWidthPixels, imageRectHeightPixels ) );
135  }
136  else
137  {
138  boundRectWidthMM = rect().width();
139  boundRectHeightMM = rect().height();
140  imageRect = QRect( 0, 0, rect().width() * mComposition->printResolution() / 25.4,
141  rect().height() * mComposition->printResolution() / 25.4 );
142  }
143  painter->save();
144  //antialiasing on
145  painter->setRenderHint( QPainter::Antialiasing, true );
146 
147  //zoom mode - calculate anchor point and rotation
148  if ( mResizeMode == Zoom )
149  {
150  //TODO - allow placement modes with rotation set. for now, setting a rotation
151  //always places picture in center of frame
152  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
153  {
154  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
155  painter->rotate( mPictureRotation );
156  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
157  }
158  else
159  {
160  //shift painter to edge/middle of frame depending on placement
161  double diffX = rect().width() - boundRectWidthMM;
162  double diffY = rect().height() - boundRectHeightMM;
163 
164  double dX = 0;
165  double dY = 0;
166  switch ( mPictureAnchor )
167  {
168  case UpperLeft:
169  case MiddleLeft:
170  case LowerLeft:
171  //nothing to do
172  break;
173  case UpperMiddle:
174  case Middle:
175  case LowerMiddle:
176  dX = diffX / 2.0;
177  break;
178  case UpperRight:
179  case MiddleRight:
180  case LowerRight:
181  dX = diffX;
182  break;
183  }
184  switch ( mPictureAnchor )
185  {
186  case UpperLeft:
187  case UpperMiddle:
188  case UpperRight:
189  //nothing to do
190  break;
191  case MiddleLeft:
192  case Middle:
193  case MiddleRight:
194  dY = diffY / 2.0;
195  break;
196  case LowerLeft:
197  case LowerMiddle:
198  case LowerRight:
199  dY = diffY;
200  break;
201  }
202  painter->translate( dX, dY );
203  }
204  }
205  else if ( mResizeMode == ZoomResizeFrame )
206  {
207  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
208  {
209  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
210  painter->rotate( mPictureRotation );
211  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
212  }
213  }
214 
215  if ( mMode == SVG )
216  {
217  mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
218  }
219  else if ( mMode == RASTER )
220  {
221  painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
222  }
223 
224  painter->restore();
225  }
226 
227  //frame and selection boxes
228  drawFrame( painter );
229  if ( isSelected() )
230  {
231  drawSelectionBoxes( painter );
232  }
233 }
234 
235 QRect QgsComposerPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
236 {
237  int boundRectWidthPixels = boundRectWidthMM * mComposition->printResolution() / 25.4;
238  int boundRectHeightPixels = boundRectHeightMM * mComposition->printResolution() / 25.4;
239 
240  //update boundRectWidth/Height so that they exactly match pixel bounds
241  boundRectWidthMM = boundRectWidthPixels * 25.4 / mComposition->printResolution();
242  boundRectHeightMM = boundRectHeightPixels * 25.4 / mComposition->printResolution();
243 
244  //calculate part of image which fits in bounds
245  int leftClip = 0;
246  int topClip = 0;
247 
248  //calculate left crop
249  switch ( mPictureAnchor )
250  {
251  case UpperLeft:
252  case MiddleLeft:
253  case LowerLeft:
254  leftClip = 0;
255  break;
256  case UpperMiddle:
257  case Middle:
258  case LowerMiddle:
259  leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
260  break;
261  case UpperRight:
262  case MiddleRight:
263  case LowerRight:
264  leftClip = imageRectPixels.width() - boundRectWidthPixels;
265  break;
266  }
267 
268  //calculate top crop
269  switch ( mPictureAnchor )
270  {
271  case UpperLeft:
272  case UpperMiddle:
273  case UpperRight:
274  topClip = 0;
275  break;
276  case MiddleLeft:
277  case Middle:
278  case MiddleRight:
279  topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
280  break;
281  case LowerLeft:
282  case LowerMiddle:
283  case LowerRight:
284  topClip = imageRectPixels.height() - boundRectHeightPixels;
285  break;
286  }
287 
288  return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
289 }
290 
292 {
294  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
295 
296  QString source = mSourcePath;
297 
298  //data defined source set?
299  mHasExpressionError = false;
301  {
302  bool ok = false;
303  source = mDataDefinedProperties.valueAsString( QgsComposerObject::PictureSource, *evalContext, source, &ok );
304  if ( ok )
305  {
306  source = source.trimmed();
307  QgsDebugMsg( QString( "exprVal PictureSource:%1" ).arg( source ) );
308  }
309  else
310  {
311  mHasExpressionError = true;
312  source = QString();
313  QgsMessageLog::logMessage( tr( "Picture expression eval error" ) );
314  }
315  }
316 
317  loadPicture( source );
318 }
319 
320 void QgsComposerPicture::loadRemotePicture( const QString &url )
321 {
322  //remote location
323 
324  QgsNetworkContentFetcher fetcher;
325  //pause until HTML fetch
326  mLoaded = false;
327  fetcher.fetchContent( QUrl( url ) );
328  connect( &fetcher, &QgsNetworkContentFetcher::finished, this, &QgsComposerPicture::remotePictureLoaded );
329 
330  while ( !mLoaded )
331  {
332  qApp->processEvents();
333  }
334 
335  QNetworkReply *reply = fetcher.reply();
336  if ( reply )
337  {
338  QImageReader imageReader( reply );
339  mImage = imageReader.read();
340  mMode = RASTER;
341  reply->deleteLater();
342  }
343  else
344  {
345  mMode = Unknown;
346  }
347 }
348 
349 void QgsComposerPicture::loadLocalPicture( const QString &path )
350 {
351  QFile pic;
352  pic.setFileName( path );
353 
354  if ( !pic.exists() )
355  {
356  mMode = Unknown;
357  }
358  else
359  {
360  QFileInfo sourceFileInfo( pic );
361  QString sourceFileSuffix = sourceFileInfo.suffix();
362  if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
363  {
364  //try to open svg
366  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsComposerObject::PictureSvgBackgroundColor, context, mSvgFillColor );
367  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsComposerObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
368  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
369  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
370  1.0 );
371  mSVG.load( svgContent );
372  if ( mSVG.isValid() )
373  {
374  mMode = SVG;
375  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
376  mDefaultSvgSize.setWidth( viewBox.width() );
377  mDefaultSvgSize.setHeight( viewBox.height() );
378  }
379  else
380  {
381  mMode = Unknown;
382  }
383  }
384  else
385  {
386  //try to open raster with QImageReader
387  QImageReader imageReader( pic.fileName() );
388  if ( imageReader.read( &mImage ) )
389  {
390  mMode = RASTER;
391  }
392  else
393  {
394  mMode = Unknown;
395  }
396  }
397  }
398 
399 }
400 
401 void QgsComposerPicture::remotePictureLoaded()
402 {
403  mLoaded = true;
404 }
405 
406 void QgsComposerPicture::updateMapRotation()
407 {
408  if ( !mRotationMap )
409  return;
410 
411  // take map rotation
412  double rotation = mRotationMap->mapRotation();
413 
414  // handle true north
415  switch ( mNorthMode )
416  {
417  case GridNorth:
418  break; // nothing to do
419 
420  case TrueNorth:
421  {
422  QgsPointXY center = mRotationMap->currentMapExtent()->center();
423  QgsCoordinateReferenceSystem crs = mRotationMap->crs();
424 
425  try
426  {
427  double bearing = QgsBearingUtils::bearingTrueNorth( crs, center );
428  rotation += bearing;
429  }
430  catch ( QgsException &e )
431  {
432  Q_UNUSED( e );
433  QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
434  }
435  break;
436  }
437  }
438 
439  rotation += mNorthOffset;
440  setPictureRotation( rotation );
441 }
442 
443 void QgsComposerPicture::loadPicture( const QString &path )
444 {
445  if ( path.startsWith( QLatin1String( "http" ) ) )
446  {
447  //remote location
448  loadRemotePicture( path );
449  }
450  else
451  {
452  //local location
453  loadLocalPicture( path );
454  }
455  if ( mMode != Unknown ) //make sure we start with a new QImage
456  {
457  recalculateSize();
458  }
459  else if ( mHasExpressionError || !( path.isEmpty() ) )
460  {
461  //trying to load an invalid file or bad expression, show cross picture
462  mMode = SVG;
463  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
464  mSVG.load( badFile );
465  if ( mSVG.isValid() )
466  {
467  mMode = SVG;
468  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
469  mDefaultSvgSize.setWidth( viewBox.width() );
470  mDefaultSvgSize.setHeight( viewBox.height() );
471  recalculateSize();
472  }
473  }
474 
475  emit itemChanged();
476 }
477 
478 QRectF QgsComposerPicture::boundedImageRect( double deviceWidth, double deviceHeight )
479 {
480  double imageToDeviceRatio;
481  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
482  {
483  imageToDeviceRatio = deviceWidth / mImage.width();
484  double height = imageToDeviceRatio * mImage.height();
485  return QRectF( 0, 0, deviceWidth, height );
486  }
487  else
488  {
489  imageToDeviceRatio = deviceHeight / mImage.height();
490  double width = imageToDeviceRatio * mImage.width();
491  return QRectF( 0, 0, width, deviceHeight );
492  }
493 }
494 
495 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
496 {
497  double imageToSvgRatio;
498  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
499  {
500  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
501  double width = mDefaultSvgSize.width() * imageToSvgRatio;
502  return QRectF( 0, 0, width, deviceHeight );
503  }
504  else
505  {
506  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
507  double height = mDefaultSvgSize.height() * imageToSvgRatio;
508  return QRectF( 0, 0, deviceWidth, height );
509  }
510 }
511 
512 QSizeF QgsComposerPicture::pictureSize()
513 {
514  if ( mMode == SVG )
515  {
516  return mDefaultSvgSize;
517  }
518  else if ( mMode == RASTER )
519  {
520  return QSizeF( mImage.width(), mImage.height() );
521  }
522  else
523  {
524  return QSizeF( 0, 0 );
525  }
526 }
527 
528 #if 0
529 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
530 {
531  double imageToSvgRatio;
532  if ( deviceWidth / mDefaultSvgSize.width() < deviceHeight / mDefaultSvgSize.height() )
533  {
534  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
535  double height = mDefaultSvgSize.height() * imageToSvgRatio;
536  return QRectF( 0, 0, deviceWidth, height );
537  }
538  else
539  {
540  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
541  double width = mDefaultSvgSize.width() * imageToSvgRatio;
542  return QRectF( 0, 0, width, deviceHeight );
543  }
544 }
545 #endif //0
546 
547 void QgsComposerPicture::setSceneRect( const QRectF &rectangle )
548 {
549  QSizeF currentPictureSize = pictureSize();
550 
551  if ( mResizeMode == QgsComposerPicture::Clip )
552  {
553  QgsComposerItem::setSceneRect( rectangle );
554  mPictureWidth = rectangle.width();
555  mPictureHeight = rectangle.height();
556  }
557  else
558  {
559  QRectF newRect = rectangle;
560 
561  if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
562  {
563  QSizeF targetImageSize;
564  if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
565  {
566  targetImageSize = currentPictureSize;
567  }
568  else
569  {
570  //calculate aspect ratio of bounds of rotated image
571  QTransform tr;
572  tr.rotate( mPictureRotation );
573  QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
574  targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
575  }
576 
577  //if height has changed more than width, then fix width and set height correspondingly
578  //else, do the opposite
579  if ( std::fabs( rect().width() - rectangle.width() ) <
580  std::fabs( rect().height() - rectangle.height() ) )
581  {
582  newRect.setHeight( targetImageSize.height() * newRect.width() / targetImageSize.width() );
583  }
584  else
585  {
586  newRect.setWidth( targetImageSize.width() * newRect.height() / targetImageSize.height() );
587  }
588  }
589  else if ( mResizeMode == FrameToImageSize )
590  {
591  if ( !( currentPictureSize.isEmpty() ) )
592  {
593  newRect.setWidth( currentPictureSize.width() * 25.4 / mComposition->printResolution() );
594  newRect.setHeight( currentPictureSize.height() * 25.4 / mComposition->printResolution() );
595  }
596  }
597 
598  //find largest scaling of picture with this rotation which fits in item
599  if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
600  {
601  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), newRect, mPictureRotation );
602  mPictureWidth = rotatedImageRect.width();
603  mPictureHeight = rotatedImageRect.height();
604  }
605  else
606  {
607  mPictureWidth = newRect.width();
608  mPictureHeight = newRect.height();
609  }
610 
612  emit itemChanged();
613  }
614 
615  if ( mMode == SVG && !mLoadingSvg )
616  {
617  mLoadingSvg = true;
618  refreshPicture();
619  mLoadingSvg = false;
620  }
621 }
622 
624 {
625  double oldRotation = mPictureRotation;
626  mPictureRotation = rotation;
627 
628  if ( mResizeMode == Zoom )
629  {
630  //find largest scaling of picture with this rotation which fits in item
631  QSizeF currentPictureSize = pictureSize();
632  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
633  mPictureWidth = rotatedImageRect.width();
634  mPictureHeight = rotatedImageRect.height();
635  update();
636  }
637  else if ( mResizeMode == ZoomResizeFrame )
638  {
639  QSizeF currentPictureSize = pictureSize();
640  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
641 
642  //calculate actual size of image inside frame
643  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
644 
645  //rotate image rect by new rotation and get bounding box
646  QTransform tr;
647  tr.rotate( mPictureRotation );
648  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
649 
650  //keep the center in the same location
651  newRect.moveCenter( oldRect.center() );
653  emit itemChanged();
654  }
655 
656  emit pictureRotationChanged( mPictureRotation );
657 }
658 
659 void QgsComposerPicture::setRotationMap( int composerMapId )
660 {
661  if ( !mComposition )
662  {
663  return;
664  }
665 
666  if ( composerMapId == -1 ) //disable rotation from map
667  {
668  disconnect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
669  disconnect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
670  mRotationMap = nullptr;
671  }
672 
673  const QgsComposerMap *map = mComposition->getComposerMapById( composerMapId );
674  if ( !map )
675  {
676  return;
677  }
678  if ( mRotationMap )
679  {
680  disconnect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
681  disconnect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
682  }
683  mPictureRotation = map->mapRotation();
684  connect( map, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
685  connect( map, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
686  mRotationMap = map;
687  updateMapRotation();
688  emit pictureRotationChanged( mPictureRotation );
689 }
690 
692 {
693  mResizeMode = mode;
695  || ( mode == QgsComposerPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
696  {
697  //call set scene rect to force item to resize to fit picture
698  recalculateSize();
699  }
700  update();
701 }
702 
704 {
705  //call set scene rect with current position/size, as this will trigger the
706  //picture item to recalculate its frame and image size
707  setSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
708 }
709 
711 {
713  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
714 
717  || property == QgsComposerObject::AllProperties )
718  {
719  refreshPicture( evalContext );
720  }
721 
722  QgsComposerItem::refreshDataDefinedProperty( property, evalContext );
723 }
724 
725 void QgsComposerPicture::setPicturePath( const QString &path )
726 {
727  mSourcePath = path;
728  refreshPicture();
729 }
730 
732 {
733  return mSourcePath;
734 }
735 
736 bool QgsComposerPicture::writeXml( QDomElement &elem, QDomDocument &doc ) const
737 {
738  if ( elem.isNull() )
739  {
740  return false;
741  }
742  QDomElement composerPictureElem = doc.createElement( QStringLiteral( "ComposerPicture" ) );
743  QString imagePath = mSourcePath;
744  if ( mComposition )
745  {
746  // convert from absolute path to relative. For SVG we also need to consider system SVG paths
747  QgsPathResolver pathResolver = mComposition->project()->pathResolver();
748  if ( imagePath.endsWith( ".svg", Qt::CaseInsensitive ) )
749  imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
750  else
751  imagePath = pathResolver.writePath( imagePath );
752  }
753  composerPictureElem.setAttribute( QStringLiteral( "file" ), imagePath );
754  composerPictureElem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
755  composerPictureElem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
756  composerPictureElem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
757  composerPictureElem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
758  composerPictureElem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
759  composerPictureElem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
760  composerPictureElem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
761 
762  //rotation
763  composerPictureElem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
764  if ( !mRotationMap )
765  {
766  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), -1 );
767  }
768  else
769  {
770  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), mRotationMap->id() );
771  }
772  composerPictureElem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
773  composerPictureElem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
774 
775  _writeXml( composerPictureElem, doc );
776  elem.appendChild( composerPictureElem );
777  return true;
778 }
779 
780 bool QgsComposerPicture::readXml( const QDomElement &itemElem, const QDomDocument &doc )
781 {
782  if ( itemElem.isNull() )
783  {
784  return false;
785  }
786 
787  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
788  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
789  mResizeMode = QgsComposerPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
790  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
791  mPictureAnchor = static_cast< QgsComposerItem::ItemPositionMode >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsComposerItem::Middle ) ).toInt() );
792 
793  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
794  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
795  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
796 
797  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
798  if ( !composerItemList.isEmpty() )
799  {
800  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
801 
802  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
803  {
804  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
805  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
806  }
807 
808  _readXml( composerItemElem, doc );
809  }
810 
811  mDefaultSvgSize = QSize( 0, 0 );
812 
813  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
814  {
815  //update pre 2.5 picture expression to use data defined expression
816  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QLatin1String( "" ) );
817  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
818  bool expressionActive;
819  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
820 
822  }
823 
824  QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
825  if ( mComposition )
826  {
827  // convert from relative path to absolute. For SVG we also need to consider system SVG paths
828  QgsPathResolver pathResolver = mComposition->project()->pathResolver();
829  if ( imagePath.endsWith( ".svg", Qt::CaseInsensitive ) )
830  imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
831  else
832  imagePath = pathResolver.readPath( imagePath );
833  }
834  mSourcePath = imagePath;
835 
836  //picture rotation
837  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
838  {
839  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
840  }
841 
842  //rotation map
843  mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
844  mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
845 
846  int rotationMapId = itemElem.attribute( QStringLiteral( "mapId" ), QStringLiteral( "-1" ) ).toInt();
847  if ( rotationMapId == -1 )
848  {
849  mRotationMap = nullptr;
850  }
851  else if ( mComposition )
852  {
853 
854  if ( mRotationMap )
855  {
856  disconnect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
857  disconnect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
858  }
859  mRotationMap = mComposition->getComposerMapById( rotationMapId );
860  connect( mRotationMap, &QgsComposerMap::mapRotationChanged, this, &QgsComposerPicture::updateMapRotation );
861  connect( mRotationMap, &QgsComposerMap::extentChanged, this, &QgsComposerPicture::updateMapRotation );
862  }
863 
864  refreshPicture();
865 
866  emit itemChanged();
867  return true;
868 }
869 
871 {
872  if ( !mRotationMap )
873  {
874  return -1;
875  }
876  else
877  {
878  return mRotationMap->id();
879  }
880 }
881 
883 {
884  mNorthMode = mode;
885  updateMapRotation();
886 }
887 
889 {
890  mNorthOffset = offset;
891  updateMapRotation();
892 }
893 
895 {
896  mPictureAnchor = anchor;
897  update();
898 }
899 
900 void QgsComposerPicture::setSvgFillColor( const QColor &color )
901 {
902  mSvgFillColor = color;
903  refreshPicture();
904 }
905 
906 void QgsComposerPicture::setSvgStrokeColor( const QColor &color )
907 {
908  mSvgStrokeColor = color;
909  refreshPicture();
910 }
911 
913 {
914  mSvgStrokeWidth = width;
915  refreshPicture();
916 }
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:42
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:210
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.
A composer class that displays svg files or raster format (jpg, png, ...)
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...
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:151
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.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor)
Get SVG content.
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:33
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.