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